/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Windows 86Box Settings dialog handler.
 *
 *
 *
 * Authors: Miran Grca, <mgrca8@gmail.com>
 *          David Hrdlička, <hrdlickadavid@outlook.com>
 *          Jasmine Iwanek, <jriwanek@gmail.com>
 *
 *          Copyright 2016-2019 Miran Grca.
 *          Copyright 2018-2019 David Hrdlička.
 *          Copyright 2021 Laci bá'
 *          Copyright 2021-2023 Jasmine Iwanek.
 */
#define UNICODE
#define BITMAP WINDOWS_BITMAP
#include <windows.h>
#include <windowsx.h>
#include <uxtheme.h>
#undef BITMAP
#ifdef ENABLE_SETTINGS_LOG
#    include <assert.h>
#endif
#include <commctrl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/config.h>
#include "cpu.h"
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/device.h>
#include <86box/timer.h>
#include <86box/cassette.h>
#include <86box/nvr.h>
#include <86box/machine.h>
#include <86box/gameport.h>
#include <86box/isamem.h>
#include <86box/isartc.h>
#include <86box/lpt.h>
#include <86box/mouse.h>
#include <86box/serial.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/cdrom.h>
#include <86box/hdd.h>
#include <86box/hdc.h>
#include <86box/hdc_ide.h>
#include <86box/zip.h>
#include <86box/mo.h>
#include <86box/fdd.h>
#include <86box/fdc.h>
#include <86box/fdc_ext.h>
#include <86box/thread.h>
#include <86box/network.h>
#include <86box/sound.h>
#include <86box/midi.h>
#include <86box/snd_mpu401.h>
#include <86box/snd_opl.h>
#include <86box/video.h>
#include <86box/vid_xga_device.h>
#include <86box/plat.h>
#include <86box/ui.h>
#include <86box/win.h>
#include <86box/serial_passthrough.h>
#include "../disk/minivhd/minivhd.h"

/* Icon, Bus, File, C, H, S, Size, Speed */
#define C_COLUMNS_HARD_DISKS    7

#define C_COLUMNS_FLOPPY_DRIVES 3
#define C_COLUMNS_CDROM_DRIVES  3
#define C_COLUMNS_MO_DRIVES     2
#define C_COLUMNS_ZIP_DRIVES    2

static int first_cat = 0;

/* Machine category */
static int           temp_machine_type;
static int           temp_machine;
static int           temp_cpu;
static int           temp_wait_states;
static int           temp_fpu;
static int           temp_sync;
static cpu_family_t *temp_cpu_f;
static uint32_t      temp_mem_size;
#ifdef USE_DYNAREC
static int temp_dynarec;
#endif
static int temp_fpu_softfloat;

/* Video category */
static int temp_gfxcard[2];
static int temp_ibm8514;
static int temp_voodoo;
static int temp_xga;

/* Input devices category */
static int temp_mouse;
static int temp_joystick;

/* Sound category */
static int temp_sound_card[SOUND_CARD_MAX];
static int temp_midi_output_device;
static int temp_midi_input_device;
static int temp_mpu401;
static int temp_float;
static int temp_fm_driver;

/* Network category */
static int      temp_net_type[NET_CARD_MAX];
static uint16_t temp_net_card[NET_CARD_MAX];
static char     temp_pcap_dev[NET_CARD_MAX][128];

/* Ports category */
static int     temp_lpt_devices[PARALLEL_MAX];
static uint8_t temp_serial[SERIAL_MAX];
static uint8_t temp_lpt[PARALLEL_MAX];
static int     temp_serial_passthrough_enabled[SERIAL_MAX];

/* Other peripherals category */
static int temp_fdc_card;
static int temp_hdc;
static int temp_ide_ter;
static int temp_ide_qua;
static int temp_cassette;
static int temp_scsi_card[SCSI_BUS_MAX];
static int temp_bugger;
static int temp_postcard;
static int temp_isartc;
static int temp_isamem[ISAMEM_MAX];

static uint8_t temp_deviceconfig;

/* Hard disks category */
static hard_disk_t temp_hdd[HDD_NUM];

/* Floppy drives category */
static int temp_fdd_types[FDD_NUM];
static int temp_fdd_turbo[FDD_NUM];
static int temp_fdd_check_bpb[FDD_NUM];

/* Other removable devices category */
static cdrom_t     temp_cdrom[CDROM_NUM];
static zip_drive_t temp_zip_drives[ZIP_NUM];
static mo_drive_t  temp_mo_drives[MO_NUM];

static HWND hwndParentDialog;
static HWND hwndChildDialog;

static uint32_t displayed_category = 0;

extern int is486;
static int listtomachinetype[256];
static int listtomachine[256];
static int listtocpufamily[256];
static int listtocpu[256];
static int settings_list_to_device[2][256];
static int settings_list_to_fdc[20];
static int settings_list_to_midi[20];
static int settings_list_to_midi_in[20];
static int settings_list_to_hdc[20];

static int      max_spt = 63;
static int      max_hpc = 255;
static int      max_tracks = 266305;
static uint64_t mfm_tracking;
static uint64_t esdi_tracking;
static uint64_t xta_tracking;
static uint64_t ide_tracking;
static uint64_t scsi_tracking[8];
static uint64_t size;
static int      hd_listview_items;
static int      hdc_id_to_listview_index[HDD_NUM];
static int      no_update = 0;
static int      existing = 0;
static int      chs_enabled = 0;
static int      lv1_current_sel;
static int      lv2_current_sel;
static int      hard_disk_added = 0;
static int      next_free_id = 0;
static int      selection = 127;
static int      spt;
static int      hpc;
static int      tracks;
static int      ignore_change = 0;

static hard_disk_t  new_hdd;
static hard_disk_t *hdd_ptr;

static wchar_t hd_file_name[512];
static WCHAR   device_name[512];

static int
settings_get_check(HWND hdlg, int id)
{
    return SendMessage(GetDlgItem(hdlg, id), BM_GETCHECK, 0, 0);
}

static int
settings_get_cur_sel(HWND hdlg, int id)
{
    return SendMessage(GetDlgItem(hdlg, id), CB_GETCURSEL, 0, 0);
}

static void
settings_set_check(HWND hdlg, int id, int val)
{
    SendMessage(GetDlgItem(hdlg, id), BM_SETCHECK, val, 0);
}

static void
settings_set_cur_sel(HWND hdlg, int id, int val)
{
    SendMessage(GetDlgItem(hdlg, id), CB_SETCURSEL, val, 0);
}

static void
settings_reset_content(HWND hdlg, int id)
{
    SendMessage(GetDlgItem(hdlg, id), CB_RESETCONTENT, 0, 0);
}

static void
settings_add_string(HWND hdlg, int id, LPARAM string)
{
    SendMessage(GetDlgItem(hdlg, id), CB_ADDSTRING, 0, string);
}

static void
settings_enable_window(HWND hdlg, int id, int condition)
{
    EnableWindow(GetDlgItem(hdlg, id), condition ? TRUE : FALSE);
}

static void
settings_show_window(HWND hdlg, int id, int condition)
{
    HWND h;

    h = GetDlgItem(hdlg, id);
    EnableWindow(h, condition ? TRUE : FALSE);
    ShowWindow(h, condition ? SW_SHOW : SW_HIDE);
}

static void
settings_listview_enable_styles(HWND hdlg, int id)
{
    HWND h;

    h = GetDlgItem(hdlg, id);
    SetWindowTheme(h, L"Explorer", NULL);
    ListView_SetExtendedListViewStyle(h, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
}

static void
settings_listview_select(HWND hdlg, int id, int selection)
{
    HWND h;

    h = GetDlgItem(hdlg, id);
    ListView_SetItemState(h, selection, LVIS_FOCUSED | LVIS_SELECTED, 0x000F);
}

static void
settings_process_messages(void)
{
    MSG msg;
    while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

static BOOL
image_list_init(HWND hdlg, int id, const uint8_t *icon_ids)
{
    HICON      hiconItem;
    HIMAGELIST hSmall;
    HWND       hwndList = GetDlgItem(hdlg, id);

    int i = 0;

    hSmall = ListView_GetImageList(hwndList, LVSIL_SMALL);
    if (hSmall != 0)
        ImageList_Destroy(hSmall);

    hSmall = ImageList_Create(win_get_system_metrics(SM_CXSMICON, dpi),
                              win_get_system_metrics(SM_CYSMICON, dpi),
                              ILC_MASK | ILC_COLOR32, 1, 1);

    while (1) {
        if (icon_ids[i] == 0)
            break;

        hiconItem = hIcon[icon_ids[i]];
        ImageList_AddIcon(hSmall, hiconItem);

        i++;
    }

    ListView_SetImageList(hwndList, hSmall, LVSIL_SMALL);

    return TRUE;
}

/* Show a MessageBox dialog.  This is nasty, I know.  --FvK */
static int
settings_msgbox_header(int flags, void *header, void *message)
{
    HWND h;
    int  i;

    h        = hwndMain;
    hwndMain = hwndParentDialog;

    i = ui_msgbox_header(flags, header, message);

    hwndMain = h;

    return i;
}

static int
settings_msgbox_ex(int flags, void *header, void *message, void *btn1, void *btn2, void *btn3)
{
    HWND h;
    int  i;

    h        = hwndMain;
    hwndMain = hwndParentDialog;

    i = ui_msgbox_ex(flags, header, message, btn1, btn2, btn3);

    hwndMain = h;

    return i;
}

/* This does the initial read of global variables into the temporary ones. */
static void
win_settings_init(void)
{
    int i = 0;

    /* Machine category */
    temp_machine_type = machine_get_type(machine);
    temp_machine      = machine;
    temp_cpu_f        = cpu_f;
    temp_wait_states  = cpu_waitstates;
    temp_cpu          = cpu;
    temp_mem_size     = mem_size;
#ifdef USE_DYNAREC
    temp_dynarec = cpu_use_dynarec;
#endif
    temp_fpu_softfloat = fpu_softfloat;
    temp_fpu  = fpu_type;
    temp_sync = time_sync;

    /* Video category */
    temp_gfxcard[0] = gfxcard[0];
    temp_gfxcard[1] = gfxcard[1];
    temp_voodoo     = voodoo_enabled;
    temp_ibm8514    = ibm8514_standalone_enabled;
    temp_xga        = xga_standalone_enabled;

    /* Input devices category */
    temp_mouse    = mouse_type;
    temp_joystick = joystick_type;

    /* Sound category */
    for (i = 0; i < SOUND_CARD_MAX; i++)
        temp_sound_card[i] = sound_card_current[i];
    temp_midi_output_device = midi_output_device_current;
    temp_midi_input_device  = midi_input_device_current;
    temp_mpu401             = mpu401_standalone_enable;
    temp_float              = sound_is_float;
    temp_fm_driver          = fm_driver;

    /* Network category */
    for (i = 0; i < NET_CARD_MAX; i++) {
        temp_net_type[i] = net_cards_conf[i].net_type;
        memset(temp_pcap_dev[i], 0, sizeof(temp_pcap_dev[i]));
#ifdef ENABLE_SETTINGS_LOG
        assert(sizeof(temp_pcap_dev[i]) == sizeof(net_cards_conf[i].host_dev_name));
#endif
        memcpy(temp_pcap_dev[i], net_cards_conf[i].host_dev_name, sizeof(net_cards_conf[i].host_dev_name));
        temp_net_card[i] = net_cards_conf[i].device_num;
    }

    /* Ports category */
    for (i = 0; i < PARALLEL_MAX; i++) {
        temp_lpt_devices[i] = lpt_ports[i].device;
        temp_lpt[i]         = lpt_ports[i].enabled;
    }
    for (i = 0; i < SERIAL_MAX; i++) {
        temp_serial[i]                     = com_ports[i].enabled;
        temp_serial_passthrough_enabled[i] = serial_passthrough_enabled[i];
    }

    /* Storage devices category */
    for (i = 0; i < SCSI_BUS_MAX; i++)
        temp_scsi_card[i] = scsi_card_current[i];
    temp_fdc_card = fdc_type;
    temp_hdc      = hdc_current;
    temp_ide_ter  = ide_ter_enabled;
    temp_ide_qua  = ide_qua_enabled;
    temp_cassette = cassette_enable;

    mfm_tracking = xta_tracking = esdi_tracking = ide_tracking = 0;
    for (i = 0; i < SCSI_LUN_MAX; i++)
        scsi_tracking[i] = 0;

    /* Hard disks category */
    memcpy(temp_hdd, hdd, HDD_NUM * sizeof(hard_disk_t));
    for (i = 0; i < HDD_NUM; i++) {
        if (hdd[i].bus == HDD_BUS_MFM)
            mfm_tracking |= (1 << (hdd[i].mfm_channel << 3));
        else if (hdd[i].bus == HDD_BUS_XTA)
            xta_tracking |= (1 << (hdd[i].xta_channel << 3));
        else if (hdd[i].bus == HDD_BUS_ESDI)
            esdi_tracking |= (1 << (hdd[i].esdi_channel << 3));
        else if ((hdd[i].bus == HDD_BUS_IDE) || (hdd[i].bus == HDD_BUS_ATAPI))
            ide_tracking |= (1 << (hdd[i].ide_channel << 3));
        else if (hdd[i].bus == HDD_BUS_SCSI)
            scsi_tracking[hdd[i].scsi_id >> 3] |= (1 << ((hdd[i].scsi_id & 0x07) << 3));
    }

    /* Floppy drives category */
    for (i = 0; i < FDD_NUM; i++) {
        temp_fdd_types[i]     = fdd_get_type(i);
        temp_fdd_turbo[i]     = fdd_get_turbo(i);
        temp_fdd_check_bpb[i] = fdd_get_check_bpb(i);
    }

    /* Other removable devices category */
    memcpy(temp_cdrom, cdrom, CDROM_NUM * sizeof(cdrom_t));
    for (i = 0; i < CDROM_NUM; i++) {
        if (cdrom[i].bus_type == CDROM_BUS_ATAPI)
            ide_tracking |= (2 << (cdrom[i].ide_channel << 3));
        else if (cdrom[i].bus_type == CDROM_BUS_SCSI)
            scsi_tracking[cdrom[i].scsi_device_id >> 3] |= (1 << ((cdrom[i].scsi_device_id & 0x07) << 3));
    }
    memcpy(temp_zip_drives, zip_drives, ZIP_NUM * sizeof(zip_drive_t));
    for (i = 0; i < ZIP_NUM; i++) {
        if (zip_drives[i].bus_type == ZIP_BUS_ATAPI)
            ide_tracking |= (4 << (zip_drives[i].ide_channel << 3));
        else if (zip_drives[i].bus_type == ZIP_BUS_SCSI)
            scsi_tracking[zip_drives[i].scsi_device_id >> 3] |= (1 << ((zip_drives[i].scsi_device_id & 0x07) << 3));
    }
    memcpy(temp_mo_drives, mo_drives, MO_NUM * sizeof(mo_drive_t));
    for (i = 0; i < MO_NUM; i++) {
        if (mo_drives[i].bus_type == MO_BUS_ATAPI)
            ide_tracking |= (1 << (mo_drives[i].ide_channel << 3));
        else if (mo_drives[i].bus_type == MO_BUS_SCSI)
            scsi_tracking[mo_drives[i].scsi_device_id >> 3] |= (1 << ((mo_drives[i].scsi_device_id & 0x07) << 3));
    }

    /* Other peripherals category */
    temp_bugger   = bugger_enabled;
    temp_postcard = postcard_enabled;
    temp_isartc   = isartc_type;

    /* ISA memory boards. */
    for (i = 0; i < ISAMEM_MAX; i++)
        temp_isamem[i] = isamem_type[i];

    temp_deviceconfig = 0;
}

/* This returns 1 if any variable has changed, 0 if not. */
static int
win_settings_changed(void)
{
    int i = 0;

    /* Machine category */
    i = i || (machine != temp_machine);
    i = i || (cpu_f != temp_cpu_f);
    i = i || (cpu_waitstates != temp_wait_states);
    i = i || (cpu != temp_cpu);
    i = i || (mem_size != temp_mem_size);
#ifdef USE_DYNAREC
    i = i || (temp_dynarec != cpu_use_dynarec);
#endif
    i = i || (temp_fpu_softfloat != fpu_softfloat);
    i = i || (temp_fpu != fpu_type);
    i = i || (temp_sync != time_sync);

    /* Video category */
    i = i || (gfxcard[0] != temp_gfxcard[0]);
    i = i || (gfxcard[1] != temp_gfxcard[1]);
    i = i || (voodoo_enabled != temp_voodoo);
    i = i || (ibm8514_standalone_enabled != temp_ibm8514);
    i = i || (xga_standalone_enabled != temp_xga);

    /* Input devices category */
    i = i || (mouse_type != temp_mouse);
    i = i || (joystick_type != temp_joystick);

    /* Sound category */
    for (uint8_t j = 0; j < SOUND_CARD_MAX; j++)
        i = i || (sound_card_current[j] != temp_sound_card[j]);
    i = i || (midi_output_device_current != temp_midi_output_device);
    i = i || (midi_input_device_current != temp_midi_input_device);
    i = i || (mpu401_standalone_enable != temp_mpu401);
    i = i || (sound_is_float != temp_float);
    i = i || (fm_driver != temp_fm_driver);

    /* Network category */
    for (uint8_t j = 0; j < NET_CARD_MAX; j++) {
        i = i || (net_cards_conf[j].net_type != temp_net_type[j]);
        i = i || strcmp(temp_pcap_dev[j], net_cards_conf[j].host_dev_name);
        i = i || (net_cards_conf[j].device_num != temp_net_card[j]);
    }

    /* Ports category */
    for (uint8_t j = 0; j < PARALLEL_MAX; j++) {
        i = i || (temp_lpt_devices[j] != lpt_ports[j].device);
        i = i || (temp_lpt[j] != lpt_ports[j].enabled);
    }
    for (uint8_t j = 0; j < SERIAL_MAX; j++) {
        i = i || (temp_serial[j] != com_ports[j].enabled);
        i = i || (temp_serial_passthrough_enabled[i] != serial_passthrough_enabled[i]);
    }

    /* Storage devices category */
    for (uint8_t j = 0; j < SCSI_BUS_MAX; j++)
        i = i || (temp_scsi_card[j] != scsi_card_current[j]);
    i = i || (fdc_type != temp_fdc_card);
    i = i || (hdc_current != temp_hdc);
    i = i || (temp_ide_ter != ide_ter_enabled);
    i = i || (temp_ide_qua != ide_qua_enabled);
    i = i || (temp_cassette != cassette_enable);

    /* Hard disks category */
    i = i || memcmp(hdd, temp_hdd, HDD_NUM * sizeof(hard_disk_t));

    /* Floppy drives category */
    for (uint8_t j = 0; j < FDD_NUM; j++) {
        i = i || (temp_fdd_types[j] != fdd_get_type(j));
        i = i || (temp_fdd_turbo[j] != fdd_get_turbo(j));
        i = i || (temp_fdd_check_bpb[j] != fdd_get_check_bpb(j));
    }

    /* Other removable devices category */
    i = i || memcmp(cdrom, temp_cdrom, CDROM_NUM * sizeof(cdrom_t));
    i = i || memcmp(zip_drives, temp_zip_drives, ZIP_NUM * sizeof(zip_drive_t));
    i = i || memcmp(mo_drives, temp_mo_drives, MO_NUM * sizeof(mo_drive_t));

    /* Other peripherals category */
    i = i || (temp_bugger != bugger_enabled);
    i = i || (temp_postcard != postcard_enabled);
    i = i || (temp_isartc != isartc_type);

    /* ISA memory boards. */
    for (uint8_t j = 0; j < ISAMEM_MAX; j++)
        i = i || (temp_isamem[j] != isamem_type[j]);

    i = i || !!temp_deviceconfig;

    return i;
}

/* This saves the settings back to the global variables. */
static void
win_settings_save(void)
{
    pc_reset_hard_close();

    /* Machine category */
    machine        = temp_machine;
    cpu_f          = temp_cpu_f;
    cpu_waitstates = temp_wait_states;
    cpu            = temp_cpu;
    mem_size       = temp_mem_size;
#ifdef USE_DYNAREC
    cpu_use_dynarec = temp_dynarec;
#endif
    fpu_softfloat  = temp_fpu_softfloat;
    fpu_type  = temp_fpu;
    time_sync = temp_sync;

    /* Video category */
    gfxcard[0]                 = temp_gfxcard[0];
    gfxcard[1]                 = temp_gfxcard[1];
    voodoo_enabled             = temp_voodoo;
    ibm8514_standalone_enabled = temp_ibm8514;
    xga_standalone_enabled     = temp_xga;

    /* Input devices category */
    mouse_type    = temp_mouse;
    joystick_type = temp_joystick;

    /* Sound category */
    for (uint8_t i = 0; i < SOUND_CARD_MAX; i++)
        sound_card_current[i] = temp_sound_card[i];
    midi_output_device_current = temp_midi_output_device;
    midi_input_device_current  = temp_midi_input_device;
    mpu401_standalone_enable   = temp_mpu401;
    sound_is_float             = temp_float;
    fm_driver                  = temp_fm_driver;

    /* Network category */
    for (uint8_t i = 0; i < NET_CARD_MAX; i++) {
        net_cards_conf[i].net_type = temp_net_type[i];
        memset(net_cards_conf[i].host_dev_name, '\0', sizeof(net_cards_conf[i].host_dev_name));
        strcpy(net_cards_conf[i].host_dev_name, temp_pcap_dev[i]);
        net_cards_conf[i].device_num = temp_net_card[i];
    }

    /* Ports category */
    for (uint8_t i = 0; i < PARALLEL_MAX; i++) {
        lpt_ports[i].device  = temp_lpt_devices[i];
        lpt_ports[i].enabled = temp_lpt[i];
    }
    for (uint8_t i = 0; i < SERIAL_MAX; i++) {
        com_ports[i].enabled          = temp_serial[i];
        serial_passthrough_enabled[i] = temp_serial_passthrough_enabled[i];
    }

    /* Storage devices category */
    for (uint8_t i = 0; i < SCSI_BUS_MAX; i++)
        scsi_card_current[i] = temp_scsi_card[i];
    hdc_current     = temp_hdc;
    fdc_type        = temp_fdc_card;
    ide_ter_enabled = temp_ide_ter;
    ide_qua_enabled = temp_ide_qua;
    cassette_enable = temp_cassette;

    /* Hard disks category */
    memcpy(hdd, temp_hdd, HDD_NUM * sizeof(hard_disk_t));
    for (uint8_t i = 0; i < HDD_NUM; i++)
        hdd[i].priv = NULL;

    /* Floppy drives category */
    for (uint8_t i = 0; i < FDD_NUM; i++) {
        fdd_set_type(i, temp_fdd_types[i]);
        fdd_set_turbo(i, temp_fdd_turbo[i]);
        fdd_set_check_bpb(i, temp_fdd_check_bpb[i]);
    }

    /* Removable devices category */
    memcpy(cdrom, temp_cdrom, CDROM_NUM * sizeof(cdrom_t));
    for (uint8_t i = 0; i < CDROM_NUM; i++) {
        cdrom[i].is_dir      = 0;
        cdrom[i].priv        = NULL;
        cdrom[i].ops         = NULL;
        cdrom[i].image       = NULL;
        cdrom[i].insert      = NULL;
        cdrom[i].close       = NULL;
        cdrom[i].get_volume  = NULL;
        cdrom[i].get_channel = NULL;
    }
    memcpy(zip_drives, temp_zip_drives, ZIP_NUM * sizeof(zip_drive_t));
    for (uint8_t i = 0; i < ZIP_NUM; i++) {
        zip_drives[i].fp   = NULL;
        zip_drives[i].priv = NULL;
    }
    memcpy(mo_drives, temp_mo_drives, MO_NUM * sizeof(mo_drive_t));
    for (uint8_t i = 0; i < MO_NUM; i++) {
        mo_drives[i].fp   = NULL;
        mo_drives[i].priv = NULL;
    }

    /* Other peripherals category */
    bugger_enabled   = temp_bugger;
    postcard_enabled = temp_postcard;
    isartc_type      = temp_isartc;

    /* ISA memory boards. */
    for (uint8_t i = 0; i < ISAMEM_MAX; i++)
        isamem_type[i] = temp_isamem[i];

    /* Mark configuration as changed. */
    config_changed = 2;

    pc_reset_hard_init();
}

static void
win_settings_machine_recalc_softfloat(HWND hdlg)
{
    if (temp_fpu == FPU_NONE) {
        settings_set_check(hdlg, IDC_CHECK_SOFTFLOAT, FALSE);
        settings_enable_window(hdlg, IDC_CHECK_SOFTFLOAT, FALSE);
    } else {
        settings_set_check(hdlg, IDC_CHECK_SOFTFLOAT, (machine_has_flags(temp_machine, MACHINE_SOFTFLOAT_ONLY) ? TRUE : temp_fpu_softfloat));
        settings_enable_window(hdlg, IDC_CHECK_SOFTFLOAT, (machine_has_flags(temp_machine, MACHINE_SOFTFLOAT_ONLY) ? FALSE : TRUE));
    }
}

static void
win_settings_machine_recalc_fpu(HWND hdlg)
{
    int         c;
    int         type;
    LPTSTR      lptsTemp;
    const char *stransi;

    lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

    settings_reset_content(hdlg, IDC_COMBO_FPU);
    c = 0;
    while (1) {
        stransi = fpu_get_name_from_index(temp_cpu_f, temp_cpu, c);
        type    = fpu_get_type_from_index(temp_cpu_f, temp_cpu, c);
        if (!stransi)
            break;

        mbstowcs(lptsTemp, stransi, strlen(stransi) + 1);
        settings_add_string(hdlg, IDC_COMBO_FPU, (LPARAM) (LPCSTR) lptsTemp);
        if (!c || (type == temp_fpu))
            settings_set_cur_sel(hdlg, IDC_COMBO_FPU, c);

        c++;
    }

    settings_enable_window(hdlg, IDC_COMBO_FPU, c > 1);

    temp_fpu = fpu_get_type_from_index(temp_cpu_f, temp_cpu, settings_get_cur_sel(hdlg, IDC_COMBO_FPU));

    win_settings_machine_recalc_softfloat(hdlg);
}

static void
win_settings_machine_recalc_cpu(HWND hdlg)
{
    int cpu_type;
#ifdef USE_DYNAREC
    int cpu_flags;
#endif

    cpu_type = temp_cpu_f->cpus[temp_cpu].cpu_type;
    settings_enable_window(hdlg, IDC_COMBO_WS, (cpu_type >= CPU_286) && (cpu_type <= CPU_386DX));

#ifdef USE_DYNAREC
    cpu_flags = temp_cpu_f->cpus[temp_cpu].cpu_flags;
    if (!(cpu_flags & CPU_SUPPORTS_DYNAREC) && (cpu_flags & CPU_REQUIRES_DYNAREC))
        fatal("Attempting to select a CPU that requires the recompiler and does not support it at the same time\n");
    if (!(cpu_flags & CPU_SUPPORTS_DYNAREC) || ((cpu_flags & CPU_REQUIRES_DYNAREC) && !cpu_override)) {
        if (!(cpu_flags & CPU_SUPPORTS_DYNAREC))
            temp_dynarec = 0;
        if (cpu_flags & CPU_REQUIRES_DYNAREC)
            temp_dynarec = 1;
        settings_set_check(hdlg, IDC_CHECK_DYNAREC, temp_dynarec);
        settings_enable_window(hdlg, IDC_CHECK_DYNAREC, FALSE);
    } else {
        settings_set_check(hdlg, IDC_CHECK_DYNAREC, temp_dynarec);
        settings_enable_window(hdlg, IDC_CHECK_DYNAREC, TRUE);
    }
#endif

    win_settings_machine_recalc_fpu(hdlg);
}

static void
win_settings_machine_recalc_cpu_m(HWND hdlg)
{
    int          c;
    int          i;
    int          first_eligible = -1;
    int          current_eligible = 0;
    int          last_eligible = 0;
    LPTSTR       lptsTemp;
    const char  *stransi;

    lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

    settings_reset_content(hdlg, IDC_COMBO_CPU_SPEED);
    c = i = 0;
    while (temp_cpu_f->cpus[c].cpu_type != 0) {
        if (cpu_is_eligible(temp_cpu_f, c, temp_machine)) {
            stransi = (char *) temp_cpu_f->cpus[c].name;
            mbstowcs(lptsTemp, stransi, strlen(stransi) + 1);
            settings_add_string(hdlg, IDC_COMBO_CPU_SPEED, (LPARAM) (LPCSTR) lptsTemp);

            if (first_eligible == -1)
                first_eligible = i;
            if (temp_cpu == c)
                current_eligible = i;
            last_eligible = i;

            listtocpu[i++] = c;
        }
        c++;
    }
    if (i == 0)
        fatal("No eligible CPUs for the selected family\n");
    settings_enable_window(hdlg, IDC_COMBO_CPU_SPEED, i != 1);
    if (current_eligible < first_eligible)
        current_eligible = first_eligible;
    else if (current_eligible > last_eligible)
        current_eligible = last_eligible;
    temp_cpu = listtocpu[current_eligible];
    settings_set_cur_sel(hdlg, IDC_COMBO_CPU_SPEED, current_eligible);

    win_settings_machine_recalc_cpu(hdlg);

    free(lptsTemp);
}

static void
win_settings_machine_recalc_machine(HWND hdlg)
{
    HWND            h;
    int             c;
    int             i;
    int             current_eligible;
    LPTSTR          lptsTemp;
    char           *stransi;
    UDACCEL         accel;
    const device_t *d;

    lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

    d = (device_t *) machine_get_device(temp_machine);
    settings_enable_window(hdlg, IDC_CONFIGURE_MACHINE, d && d->config);

    settings_reset_content(hdlg, IDC_COMBO_CPU_TYPE);
    c = i            = 0;
    current_eligible = -1;
    while (cpu_families[c].package != 0) {
        if (cpu_family_is_eligible(&cpu_families[c], temp_machine)) {
            stransi = malloc(strlen((char *) cpu_families[c].manufacturer) + strlen((char *) cpu_families[c].name) + 2);
            sprintf(stransi, "%s %s", (char *) cpu_families[c].manufacturer, (char *) cpu_families[c].name);
            mbstowcs(lptsTemp, stransi, strlen(stransi) + 1);
            free(stransi);
            settings_add_string(hdlg, IDC_COMBO_CPU_TYPE, (LPARAM) (LPCSTR) lptsTemp);
            if (&cpu_families[c] == temp_cpu_f)
                current_eligible = i;
            listtocpufamily[i++] = c;
        }
        c++;
    }
    if (i == 0)
        fatal("No eligible CPU families for the selected machine\n");
    settings_enable_window(hdlg, IDC_COMBO_CPU_TYPE, TRUE);
    if (current_eligible == -1) {
        temp_cpu_f = (cpu_family_t *) &cpu_families[listtocpufamily[0]];
        settings_set_cur_sel(hdlg, IDC_COMBO_CPU_TYPE, 0);
    } else {
        settings_set_cur_sel(hdlg, IDC_COMBO_CPU_TYPE, current_eligible);
    }
    settings_enable_window(hdlg, IDC_COMBO_CPU_TYPE, i != 1);

    win_settings_machine_recalc_cpu_m(hdlg);

    if (machine_get_ram_granularity(temp_machine) & 1023) {
        /* KB granularity */
        h = GetDlgItem(hdlg, IDC_MEMSPIN);
        SendMessage(h, UDM_SETRANGE, 0, (machine_get_min_ram(temp_machine) << 16) | machine_get_max_ram(temp_machine));

        accel.nSec = 0;
        accel.nInc = machine_get_ram_granularity(temp_machine);
        SendMessage(h, UDM_SETACCEL, 1, (LPARAM) &accel);

        SendMessage(h, UDM_SETPOS, 0, temp_mem_size);

        h = GetDlgItem(hdlg, IDC_TEXT_MB);
        SendMessage(h, WM_SETTEXT, 0, win_get_string(IDS_KB));
    } else {
        /* MB granularity */
        h = GetDlgItem(hdlg, IDC_MEMSPIN);
        SendMessage(h, UDM_SETRANGE, 0, (machine_get_min_ram(temp_machine) << 6) | (machine_get_max_ram(temp_machine) >> 10));

        accel.nSec = 0;
        accel.nInc = machine_get_ram_granularity(temp_machine) >> 10;

        SendMessage(h, UDM_SETACCEL, 1, (LPARAM) &accel);

        SendMessage(h, UDM_SETPOS, 0, temp_mem_size >> 10);

        h = GetDlgItem(hdlg, IDC_TEXT_MB);
        SendMessage(h, WM_SETTEXT, 0, win_get_string(IDS_MB));
    }

    settings_enable_window(hdlg, IDC_MEMSPIN, machine_get_min_ram(temp_machine) != machine_get_max_ram(temp_machine));
    settings_enable_window(hdlg, IDC_MEMTEXT, machine_get_min_ram(temp_machine) != machine_get_max_ram(temp_machine));

    free(lptsTemp);
}

static char *
machine_type_get_internal_name(int id)
{
    if (id < MACHINE_TYPE_MAX)
        return "";
    else
        return NULL;
}

int
machine_type_available(int id)
{
    int c = 0;

    if ((id > 0) && (id < MACHINE_TYPE_MAX)) {
        while (machine_get_internal_name_ex(c) != NULL) {
            if (machine_available(c) && (machine_get_type(c) == id))
                return 1;
            c++;
        }
    }

    return 0;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_machine_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    HWND   h;
    HWND   h2;
    int    c;
    int    d;
    int    old_machine_type;
    LPTSTR lptsTemp;
    char  *stransi;

    switch (message) {
        case WM_INITDIALOG:
            lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_MACHINE_TYPE);
            memset(listtomachinetype, 0x00, sizeof(listtomachinetype));
            while (machine_type_get_internal_name(c) != NULL) {
                if (machine_type_available(c)) {
                    stransi = (char *) machine_types[c].name;
                    mbstowcs(lptsTemp, stransi, strlen(stransi) + 1);
                    settings_add_string(hdlg, IDC_COMBO_MACHINE_TYPE, (LPARAM) lptsTemp);
                    listtomachinetype[d] = c;
                    if (c == temp_machine_type)
                        settings_set_cur_sel(hdlg, IDC_COMBO_MACHINE_TYPE, d);
                    d++;
                }
                c++;
            }

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_MACHINE);
            memset(listtomachine, 0x00, sizeof(listtomachine));
            while (machine_get_internal_name_ex(c) != NULL) {
                if (machine_available(c) && (machine_get_type(c) == temp_machine_type)) {
                    stransi = (char *) machine_getname_ex(c);
                    mbstowcs(lptsTemp, stransi, strlen(stransi) + 1);
                    settings_add_string(hdlg, IDC_COMBO_MACHINE, (LPARAM) lptsTemp);
                    listtomachine[d] = c;
                    if (c == temp_machine)
                        settings_set_cur_sel(hdlg, IDC_COMBO_MACHINE, d);
                    d++;
                }
                c++;
            }

            settings_add_string(hdlg, IDC_COMBO_WS, win_get_string(IDS_DEFAULT));
            for (c = 0; c < 8; c++) { /* TODO */
                wsprintf(lptsTemp, plat_get_string(IDS_WS), c);
                settings_add_string(hdlg, IDC_COMBO_WS, (LPARAM) lptsTemp);
            }

            settings_set_cur_sel(hdlg, IDC_COMBO_WS, temp_wait_states);

#ifdef USE_DYNAREC
            settings_set_check(hdlg, IDC_CHECK_DYNAREC, 0);
#endif

            settings_set_check(hdlg, IDC_CHECK_SOFTFLOAT, 0);

            h  = GetDlgItem(hdlg, IDC_MEMSPIN);
            h2 = GetDlgItem(hdlg, IDC_MEMTEXT);
            SendMessage(h, UDM_SETBUDDY, (WPARAM) h2, 0);

            if (temp_sync & TIME_SYNC_ENABLED) {
                if (temp_sync & TIME_SYNC_UTC)
                    settings_set_check(hdlg, IDC_RADIO_TS_UTC, BST_CHECKED);
                else
                    settings_set_check(hdlg, IDC_RADIO_TS_LOCAL, BST_CHECKED);
            } else
                settings_set_check(hdlg, IDC_RADIO_TS_DISABLED, BST_CHECKED);

            win_settings_machine_recalc_machine(hdlg);

            free(lptsTemp);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_COMBO_MACHINE_TYPE:
                    if (HIWORD(wParam) == CBN_SELCHANGE) {
                        old_machine_type  = temp_machine_type;
                        temp_machine_type = listtomachinetype[settings_get_cur_sel(hdlg, IDC_COMBO_MACHINE_TYPE)];

                        lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

                        settings_reset_content(hdlg, IDC_COMBO_MACHINE);
                        c = d = 0;
                        memset(listtomachine, 0x00, sizeof(listtomachine));
                        while (machine_get_internal_name_ex(c) != NULL) {
                            if (machine_available(c) && (machine_get_type(c) == temp_machine_type)) {
                                stransi = (char *) machine_getname_ex(c);
                                mbstowcs(lptsTemp, stransi, strlen(stransi) + 1);
                                settings_add_string(hdlg, IDC_COMBO_MACHINE, (LPARAM) lptsTemp);
                                listtomachine[d] = c;
                                if (c == temp_machine)
                                    settings_set_cur_sel(hdlg, IDC_COMBO_MACHINE, d);
                                d++;
                            }
                            c++;
                        }
                        if (old_machine_type != temp_machine_type) {
                            settings_set_cur_sel(hdlg, IDC_COMBO_MACHINE, 0);
                            temp_machine = listtomachine[0];

                            win_settings_machine_recalc_machine(hdlg);
                        }

                        free(lptsTemp);
                    }
                    break;
                case IDC_COMBO_MACHINE:
                    if (HIWORD(wParam) == CBN_SELCHANGE) {
                        temp_machine = listtomachine[settings_get_cur_sel(hdlg, IDC_COMBO_MACHINE)];
                        win_settings_machine_recalc_machine(hdlg);
                    }
                    break;
                case IDC_COMBO_CPU_TYPE:
                    if (HIWORD(wParam) == CBN_SELCHANGE) {
                        temp_cpu_f = (cpu_family_t *) &cpu_families[listtocpufamily[settings_get_cur_sel(hdlg, IDC_COMBO_CPU_TYPE)]];
                        temp_cpu   = 0;
                        win_settings_machine_recalc_cpu_m(hdlg);
                    }
                    break;
                case IDC_COMBO_CPU_SPEED:
                    if (HIWORD(wParam) == CBN_SELCHANGE) {
                        temp_cpu = listtocpu[settings_get_cur_sel(hdlg, IDC_COMBO_CPU_SPEED)];
                        win_settings_machine_recalc_cpu(hdlg);
                    }
                    break;
                case IDC_COMBO_FPU:
                    if (HIWORD(wParam) == CBN_SELCHANGE) {
                        temp_fpu = fpu_get_type_from_index(temp_cpu_f, temp_cpu,
                                                           settings_get_cur_sel(hdlg, IDC_COMBO_FPU));
                    }
                    win_settings_machine_recalc_softfloat(hdlg);
                    break;
                case IDC_CONFIGURE_MACHINE:
                    temp_machine = listtomachine[settings_get_cur_sel(hdlg, IDC_COMBO_MACHINE)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) machine_get_device(temp_machine));
                    break;
            }

            return FALSE;

        case WM_SAVESETTINGS:
            lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));
            stransi  = (char *) malloc(512);

#ifdef USE_DYNAREC
            temp_dynarec = settings_get_check(hdlg, IDC_CHECK_DYNAREC);
#endif

            temp_fpu_softfloat = settings_get_check(hdlg, IDC_CHECK_SOFTFLOAT);

            if (settings_get_check(hdlg, IDC_RADIO_TS_DISABLED))
                temp_sync = TIME_SYNC_DISABLED;

            if (settings_get_check(hdlg, IDC_RADIO_TS_LOCAL))
                temp_sync = TIME_SYNC_ENABLED;

            if (settings_get_check(hdlg, IDC_RADIO_TS_UTC))
                temp_sync = TIME_SYNC_ENABLED | TIME_SYNC_UTC;

            temp_wait_states = settings_get_cur_sel(hdlg, IDC_COMBO_WS);

            h = GetDlgItem(hdlg, IDC_MEMTEXT);
            SendMessage(h, WM_GETTEXT, 255, (LPARAM) lptsTemp);
            wcstombs(stransi, lptsTemp, 512);
            sscanf(stransi, "%u", &temp_mem_size);
            if (!(machine_get_ram_granularity(temp_machine) & 1023))
                temp_mem_size = temp_mem_size << 10;
            temp_mem_size &= ~(machine_get_ram_granularity(temp_machine) - 1);
            if (temp_mem_size < machine_get_min_ram(temp_machine))
                temp_mem_size = machine_get_min_ram(temp_machine);
            else if (temp_mem_size > machine_get_max_ram(temp_machine))
                temp_mem_size = machine_get_max_ram(temp_machine);
            free(stransi);
            free(lptsTemp);

        default:
            return FALSE;
    }

    return FALSE;
}

static void
generate_device_name(const device_t *device, const char *internal_name, int bus)
{
    char         temp[512];
    const WCHAR *wtemp;

    memset(device_name, 0x00, 512 * sizeof(WCHAR));
    memset(temp, 0x00, 512);

    if (!strcmp(internal_name, "none")) {
        /* Translate "None". */
        wtemp = (WCHAR *) win_get_string(IDS_2104);
        memcpy(device_name, wtemp, (wcslen(wtemp) + 1) * sizeof(WCHAR));
        return;
    } else if (!strcmp(internal_name, "internal"))
        memcpy(temp, "Internal", 9);
    else
        device_get_name(device, bus, temp);

    mbstowcs(device_name, temp, strlen(temp) + 1);
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_video_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    int c = 0;
    int d = 0;
    int e;

    switch (message) {
        case WM_INITDIALOG:
            // Primary Video Card
            settings_reset_content(hdlg, IDC_COMBO_VIDEO);

            while (1) {
                /* Skip "internal" if machine doesn't have it. */
                if ((c == 1) && !machine_has_flags(temp_machine, MACHINE_VIDEO)) {
                    c++;
                    continue;
                }

                generate_device_name(video_card_getdevice(c), video_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (video_card_available(c) && device_is_valid(video_card_getdevice(c), temp_machine)) {
                    if (c == 0) // "None"
                        settings_add_string(hdlg, IDC_COMBO_VIDEO, win_get_string(IDS_2104));
                    else if (c == 1) // "Internal"
                        settings_add_string(hdlg, IDC_COMBO_VIDEO, win_get_string(IDS_2119));
                    else
                        settings_add_string(hdlg, IDC_COMBO_VIDEO, (LPARAM) device_name);
                    settings_list_to_device[0][d] = c;
                    if ((c == 0) || (c == temp_gfxcard[0]))
                        settings_set_cur_sel(hdlg, IDC_COMBO_VIDEO, d);
                    d++;
                }

                c++;

                settings_process_messages();
            }

            settings_enable_window(hdlg, IDC_COMBO_VIDEO, !machine_has_flags(temp_machine, MACHINE_VIDEO_ONLY));
            e = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO)];
            settings_enable_window(hdlg, IDC_CONFIGURE_VID, video_card_has_config(e));

            // Secondary Video Card
            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_VIDEO_2);

            while (1) {
                /* Skip "internal" if machine doesn't have it. */
                if ((c == 1) && !machine_has_flags(temp_machine, MACHINE_VIDEO)) {
                    c++;
                    continue;
                }

                generate_device_name(video_card_getdevice(c), video_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if ((c > 1) && (video_card_get_flags(c) == video_card_get_flags(temp_gfxcard[0]))) {
                    c++;
                    continue;
                }

                if (video_card_available(c) && device_is_valid(video_card_getdevice(c), temp_machine)) {
                    if (c == 0) // "None"
                        settings_add_string(hdlg, IDC_COMBO_VIDEO_2, win_get_string(IDS_2104));
                    else if (c == 1) // "Internal"
                        settings_add_string(hdlg, IDC_COMBO_VIDEO_2, win_get_string(IDS_2119));
                    else
                        settings_add_string(hdlg, IDC_COMBO_VIDEO_2, (LPARAM) device_name);
                    settings_list_to_device[1][d] = c;
                    if ((c == 0) || (c == temp_gfxcard[1]))
                        settings_set_cur_sel(hdlg, IDC_COMBO_VIDEO_2, d);
                    d++;
                }

                c++;

                settings_process_messages();
            }

            settings_enable_window(hdlg, IDC_COMBO_VIDEO_2, !machine_has_flags(temp_machine, MACHINE_VIDEO_ONLY));
            e = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO_2)];
            settings_enable_window(hdlg, IDC_CONFIGURE_VID_2, video_card_has_config(e));

            settings_enable_window(hdlg, IDC_CHECK_VOODOO, machine_has_bus(temp_machine, MACHINE_BUS_PCI));
            settings_set_check(hdlg, IDC_CHECK_VOODOO, temp_voodoo);
            settings_enable_window(hdlg, IDC_BUTTON_VOODOO, machine_has_bus(temp_machine, MACHINE_BUS_PCI) && temp_voodoo);

            settings_enable_window(hdlg, IDC_CHECK_IBM8514, machine_has_bus(temp_machine, MACHINE_BUS_ISA16) || machine_has_bus(temp_machine, MACHINE_BUS_MCA));
            settings_set_check(hdlg, IDC_CHECK_IBM8514, temp_ibm8514);

            settings_enable_window(hdlg, IDC_CHECK_XGA, machine_has_bus(temp_machine, MACHINE_BUS_ISA16) || machine_has_bus(temp_machine, MACHINE_BUS_MCA));
            settings_set_check(hdlg, IDC_CHECK_XGA, temp_xga);
            settings_enable_window(hdlg, IDC_BUTTON_XGA, (machine_has_bus(temp_machine, MACHINE_BUS_ISA16) || machine_has_bus(temp_machine, MACHINE_BUS_MCA)) && temp_xga);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_COMBO_VIDEO:
                    temp_gfxcard[0] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_VID, video_card_has_config(temp_gfxcard[0]));

                    // Secondary Video Card
                    c = d = 0;
                    settings_reset_content(hdlg, IDC_COMBO_VIDEO_2);

                    while (1) {
                        /* Skip "internal" if machine doesn't have it. */
                        if ((c == 1) && !machine_has_flags(temp_machine, MACHINE_VIDEO)) {
                            c++;
                            continue;
                        }

                        generate_device_name(video_card_getdevice(c), video_get_internal_name(c), 1);

                        if (!device_name[0])
                            break;

                        if ((c > 1) && (video_card_get_flags(c) == video_card_get_flags(temp_gfxcard[0]))) {
                            c++;
                            continue;
                        }

                        if (video_card_available(c) && device_is_valid(video_card_getdevice(c), temp_machine)) {
                            if (c == 0) // "None"
                                settings_add_string(hdlg, IDC_COMBO_VIDEO_2, win_get_string(IDS_2104));
                            else if (c == 1) // "Internal"
                                settings_add_string(hdlg, IDC_COMBO_VIDEO_2, win_get_string(IDS_2119));
                            else
                                settings_add_string(hdlg, IDC_COMBO_VIDEO_2, (LPARAM) device_name);
                            settings_list_to_device[1][d] = c;
                            if ((c == 0) || (c == temp_gfxcard[1]))
                                settings_set_cur_sel(hdlg, IDC_COMBO_VIDEO_2, d);
                            d++;
                        }

                        c++;

                        settings_process_messages();
                    }

                    settings_enable_window(hdlg, IDC_COMBO_VIDEO_2, !machine_has_flags(temp_machine, MACHINE_VIDEO_ONLY));
                    e = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO_2)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_VID_2, video_card_has_config(e));
                    break;

                case IDC_COMBO_VIDEO_2:
                    temp_gfxcard[1] = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO_2)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_VID_2, video_card_has_config(temp_gfxcard[1]));
                    break;

                case IDC_CHECK_VOODOO:
                    temp_voodoo = settings_get_check(hdlg, IDC_CHECK_VOODOO);
                    settings_enable_window(hdlg, IDC_BUTTON_VOODOO, temp_voodoo);
                    break;

                case IDC_CHECK_IBM8514:
                    temp_ibm8514 = settings_get_check(hdlg, IDC_CHECK_IBM8514);
                    break;

                case IDC_CHECK_XGA:
                    temp_xga = settings_get_check(hdlg, IDC_CHECK_XGA);
                    settings_enable_window(hdlg, IDC_BUTTON_XGA, temp_xga);
                    break;

                case IDC_BUTTON_VOODOO:
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) &voodoo_device);
                    break;

                case IDC_BUTTON_XGA:
                    if (machine_has_bus(temp_machine, MACHINE_BUS_MCA) > 0)
                        temp_deviceconfig |= deviceconfig_open(hdlg, (void *) &xga_device);
                    else
                        temp_deviceconfig |= deviceconfig_open(hdlg, (void *) &xga_isa_device);
                    break;

                case IDC_CONFIGURE_VID:
                    temp_gfxcard[0] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) video_card_getdevice(temp_gfxcard[0]));
                    break;

                case IDC_CONFIGURE_VID_2:
                    temp_gfxcard[1] = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO_2)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) video_card_getdevice(temp_gfxcard[1]));
                    break;
            }
            return FALSE;

        case WM_SAVESETTINGS:
            temp_gfxcard[0] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO)];
            temp_gfxcard[1] = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_VIDEO_2)];
            temp_voodoo     = settings_get_check(hdlg, IDC_CHECK_VOODOO);
            temp_ibm8514    = settings_get_check(hdlg, IDC_CHECK_IBM8514);
            temp_xga        = settings_get_check(hdlg, IDC_CHECK_XGA);

        default:
            return FALSE;
    }
    return FALSE;
}

static int
mouse_valid(int num, int m)
{
    const device_t *dev;

    if ((num == MOUSE_TYPE_INTERNAL) && !machine_has_flags(m, MACHINE_MOUSE))
        return 0;

    dev = mouse_get_device(num);
    return (device_is_valid(dev, m));
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_input_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    wchar_t     str[128];
    const char *joy_name;
    int         c;
    int         d;

    switch (message) {
        case WM_INITDIALOG:
            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_MOUSE);
            for (c = 0; c < mouse_get_ndev(); c++) {
                if (mouse_valid(c, temp_machine)) {
                    generate_device_name(mouse_get_device(c), mouse_get_internal_name(c), 0);
                    if (c == 0)
                        settings_add_string(hdlg, IDC_COMBO_MOUSE, win_get_string(IDS_2104));
                    else if (c == 1)
                        settings_add_string(hdlg, IDC_COMBO_MOUSE, win_get_string(IDS_2119));
                    else
                        settings_add_string(hdlg, IDC_COMBO_MOUSE, (LPARAM) device_name);
                    settings_list_to_device[0][d] = c;
                    if ((c == 0) || (c == temp_mouse))
                        settings_set_cur_sel(hdlg, IDC_COMBO_MOUSE, d);
                    d++;
                }
            }

            settings_enable_window(hdlg, IDC_CONFIGURE_MOUSE, mouse_has_config(temp_mouse));

            c        = 0;
            joy_name = joystick_get_name(c);
            while (joy_name) {
                mbstowcs(str, joy_name, strlen(joy_name) + 1);
                settings_add_string(hdlg, IDC_COMBO_JOYSTICK, (LPARAM) str);

                c++;
                joy_name = joystick_get_name(c);
            }
            settings_enable_window(hdlg, IDC_COMBO_JOYSTICK, TRUE);
            settings_set_cur_sel(hdlg, IDC_COMBO_JOYSTICK, temp_joystick);

            for (c = 0; c < 4; c++)
                settings_enable_window(hdlg, IDC_JOY1 + c, joystick_get_max_joysticks(temp_joystick) > c);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_COMBO_MOUSE:
                    temp_mouse = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_MOUSE)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_MOUSE, mouse_has_config(temp_mouse));
                    break;

                case IDC_CONFIGURE_MOUSE:
                    temp_mouse = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_MOUSE)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) mouse_get_device(temp_mouse));
                    break;

                case IDC_COMBO_JOYSTICK:
                    temp_joystick = settings_get_cur_sel(hdlg, IDC_COMBO_JOYSTICK);

                    for (c = 0; c < MAX_JOYSTICKS; c++)
                        settings_enable_window(hdlg, IDC_JOY1 + c, joystick_get_max_joysticks(temp_joystick) > c);
                    break;

                case IDC_JOY1:
                case IDC_JOY2:
                case IDC_JOY3:
                case IDC_JOY4:
                    temp_joystick = settings_get_cur_sel(hdlg, IDC_COMBO_JOYSTICK);
                    temp_deviceconfig |= joystickconfig_open(hdlg, LOWORD(wParam) - IDC_JOY1, temp_joystick);
                    break;
            }
            return FALSE;

        case WM_SAVESETTINGS:
            temp_mouse    = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_MOUSE)];
            temp_joystick = settings_get_cur_sel(hdlg, IDC_COMBO_JOYSTICK);

        default:
            return FALSE;
    }
    return FALSE;
}

static int
mpu401_present(void)
{
    return temp_mpu401 ? 1 : 0;
}

int
mpu401_standalone_allow(void)
{
    const char *mdout;
    const char *mdin;

    if (!machine_has_bus(temp_machine, MACHINE_BUS_ISA) && !machine_has_bus(temp_machine, MACHINE_BUS_MCA))
        return 0;

    mdout = midi_out_device_get_internal_name(temp_midi_output_device);
    mdin  = midi_in_device_get_internal_name(temp_midi_input_device);

    if (mdout != NULL) {
        if (!strcmp(mdout, "none") && !strcmp(mdin, "none"))
            return 0;
    }

    return 1;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_sound_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    uint16_t        c;
    uint16_t        d;
    LPTSTR          lptsTemp;
    const device_t *sound_dev[SOUND_CARD_MAX];

    switch (message) {
        case WM_INITDIALOG:
            lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_SOUND1);
            while (1) {
                /* Skip "internal" if machine doesn't have it. */
                if ((c == 1) && !machine_has_flags(temp_machine, MACHINE_SOUND)) {
                    c++;
                    continue;
                }

                generate_device_name(sound_card_getdevice(c), sound_card_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (sound_card_available(c)) {
                    sound_dev[0] = sound_card_getdevice(c);

                    if (device_is_valid(sound_dev[0], temp_machine)) {
                        if (c == 0)
                            settings_add_string(hdlg, IDC_COMBO_SOUND1, win_get_string(IDS_2104));
                        else if (c == 1)
                            settings_add_string(hdlg, IDC_COMBO_SOUND1, win_get_string(IDS_2119));
                        else
                            settings_add_string(hdlg, IDC_COMBO_SOUND1, (LPARAM) device_name);
                        settings_list_to_device[0][d] = c;
                        if ((c == 0) || (c == temp_sound_card[0]))
                            settings_set_cur_sel(hdlg, IDC_COMBO_SOUND1, d);
                        d++;
                    }
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_COMBO_SOUND1, d);
            settings_enable_window(hdlg, IDC_CONFIGURE_SND1, sound_card_has_config(temp_sound_card[0]));

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_SOUND2);
            while (1) {
                /* Skip "internal" */
                if (c == 1) {
                    c++;
                    continue;
                }

                generate_device_name(sound_card_getdevice(c), sound_card_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (sound_card_available(c)) {
                    sound_dev[1] = sound_card_getdevice(c);

                    if (device_is_valid(sound_dev[1], temp_machine)) {
                        if (c == 0)
                            settings_add_string(hdlg, IDC_COMBO_SOUND2, win_get_string(IDS_2104));
                        else if (c == 1)
                            settings_add_string(hdlg, IDC_COMBO_SOUND2, win_get_string(IDS_2119));
                        else
                            settings_add_string(hdlg, IDC_COMBO_SOUND2, (LPARAM) device_name);
                        settings_list_to_device[0][d] = c;
                        if ((c == 0) || (c == temp_sound_card[1]))
                            settings_set_cur_sel(hdlg, IDC_COMBO_SOUND2, d);
                        d++;
                    }
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_COMBO_SOUND2, d);
            settings_enable_window(hdlg, IDC_CONFIGURE_SND2, sound_card_has_config(temp_sound_card[1]));

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_SOUND3);
            while (1) {
                /* Skip "internal" */
                if (c == 1) {
                    c++;
                    continue;
                }

                generate_device_name(sound_card_getdevice(c), sound_card_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (sound_card_available(c)) {
                    sound_dev[2] = sound_card_getdevice(c);

                    if (device_is_valid(sound_dev[2], temp_machine)) {
                        if (c == 0)
                            settings_add_string(hdlg, IDC_COMBO_SOUND3, win_get_string(IDS_2104));
                        else if (c == 1)
                            settings_add_string(hdlg, IDC_COMBO_SOUND3, win_get_string(IDS_2119));
                        else
                            settings_add_string(hdlg, IDC_COMBO_SOUND3, (LPARAM) device_name);
                        settings_list_to_device[0][d] = c;
                        if ((c == 0) || (c == temp_sound_card[2]))
                            settings_set_cur_sel(hdlg, IDC_COMBO_SOUND3, d);
                        d++;
                    }
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_COMBO_SOUND3, d);
            settings_enable_window(hdlg, IDC_CONFIGURE_SND3, sound_card_has_config(temp_sound_card[2]));

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_SOUND4);
            while (1) {
                /* Skip "internal" */
                if (c == 1) {
                    c++;
                    continue;
                }

                generate_device_name(sound_card_getdevice(c), sound_card_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (sound_card_available(c)) {
                    sound_dev[3] = sound_card_getdevice(c);

                    if (device_is_valid(sound_dev[3], temp_machine)) {
                        if (c == 0)
                            settings_add_string(hdlg, IDC_COMBO_SOUND4, win_get_string(IDS_2104));
                        else if (c == 1)
                            settings_add_string(hdlg, IDC_COMBO_SOUND4, win_get_string(IDS_2119));
                        else
                            settings_add_string(hdlg, IDC_COMBO_SOUND4, (LPARAM) device_name);
                        settings_list_to_device[0][d] = c;
                        if ((c == 0) || (c == temp_sound_card[3]))
                            settings_set_cur_sel(hdlg, IDC_COMBO_SOUND4, d);
                        d++;
                    }
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_COMBO_SOUND4, d);
            settings_enable_window(hdlg, IDC_CONFIGURE_SND4, sound_card_has_config(temp_sound_card[3]));

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_MIDI_OUT);
            while (1) {
                generate_device_name(midi_out_device_getdevice(c), midi_out_device_get_internal_name(c), 0);

                if (!device_name[0])
                    break;

                if (midi_out_device_available(c)) {
                    if (c == 0)
                        settings_add_string(hdlg, IDC_COMBO_MIDI_OUT, win_get_string(IDS_2104));
                    else
                        settings_add_string(hdlg, IDC_COMBO_MIDI_OUT, (LPARAM) device_name);
                    settings_list_to_midi[d] = c;
                    if ((c == 0) || (c == temp_midi_output_device))
                        settings_set_cur_sel(hdlg, IDC_COMBO_MIDI_OUT, d);
                    d++;
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_CONFIGURE_MIDI_OUT, midi_out_device_has_config(temp_midi_output_device));

            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_MIDI_IN);
            while (1) {
                generate_device_name(midi_in_device_getdevice(c), midi_in_device_get_internal_name(c), 0);

                if (!device_name[0])
                    break;

                if (midi_in_device_available(c)) {
                    if (c == 0)
                        settings_add_string(hdlg, IDC_COMBO_MIDI_IN, win_get_string(IDS_2104));
                    else
                        settings_add_string(hdlg, IDC_COMBO_MIDI_IN, (LPARAM) device_name);
                    settings_list_to_midi_in[d] = c;
                    if ((c == 0) || (c == temp_midi_input_device))
                        settings_set_cur_sel(hdlg, IDC_COMBO_MIDI_IN, d);
                    d++;
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_CONFIGURE_MIDI_IN, midi_in_device_has_config(temp_midi_input_device));
            settings_set_check(hdlg, IDC_CHECK_MPU401, temp_mpu401);
            settings_enable_window(hdlg, IDC_CHECK_MPU401, mpu401_standalone_allow());
            settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_standalone_allow() && temp_mpu401);
            settings_set_check(hdlg, IDC_CHECK_FLOAT, temp_float);

            if (temp_fm_driver == FM_DRV_YMFM)
                settings_set_check(hdlg, IDC_RADIO_FM_DRV_YMFM, BST_CHECKED);
            else
                settings_set_check(hdlg, IDC_RADIO_FM_DRV_NUKED, BST_CHECKED);

            free(lptsTemp);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_COMBO_SOUND1:
                    temp_sound_card[0] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND1)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_SND1, sound_card_has_config(temp_sound_card[0]));
                    settings_set_check(hdlg, IDC_CHECK_MPU401, temp_mpu401);
                    settings_enable_window(hdlg, IDC_CHECK_MPU401, mpu401_standalone_allow());
                    settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_standalone_allow() && temp_mpu401);
                    break;

                case IDC_CONFIGURE_SND1:
                    temp_sound_card[0] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND1)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) sound_card_getdevice(temp_sound_card[0]));
                    break;

                case IDC_COMBO_SOUND2:
                    temp_sound_card[1] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND2)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_SND2, sound_card_has_config(temp_sound_card[1]));
                    settings_set_check(hdlg, IDC_CHECK_MPU401, temp_mpu401);
                    settings_enable_window(hdlg, IDC_CHECK_MPU401, mpu401_standalone_allow());
                    settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_standalone_allow() && temp_mpu401);
                    break;

                case IDC_CONFIGURE_SND2:
                    temp_sound_card[1] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND2)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) sound_card_getdevice(temp_sound_card[1]));
                    break;

                case IDC_COMBO_SOUND3:
                    temp_sound_card[2] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND3)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_SND3, sound_card_has_config(temp_sound_card[2]));
                    settings_set_check(hdlg, IDC_CHECK_MPU401, temp_mpu401);
                    settings_enable_window(hdlg, IDC_CHECK_MPU401, mpu401_standalone_allow());
                    settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_standalone_allow() && temp_mpu401);
                    break;

                case IDC_CONFIGURE_SND3:
                    temp_sound_card[2] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND3)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) sound_card_getdevice(temp_sound_card[2]));
                    break;

                case IDC_COMBO_SOUND4:
                    temp_sound_card[3] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND4)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_SND4, sound_card_has_config(temp_sound_card[3]));
                    settings_set_check(hdlg, IDC_CHECK_MPU401, temp_mpu401);
                    settings_enable_window(hdlg, IDC_CHECK_MPU401, mpu401_standalone_allow());
                    settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_standalone_allow() && temp_mpu401);
                    break;

                case IDC_CONFIGURE_SND4:
                    temp_sound_card[3] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND4)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) sound_card_getdevice(temp_sound_card[3]));
                    break;

                case IDC_COMBO_MIDI_OUT:
                    temp_midi_output_device = settings_list_to_midi[settings_get_cur_sel(hdlg, IDC_COMBO_MIDI_OUT)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_MIDI_OUT, midi_out_device_has_config(temp_midi_output_device));
                    settings_set_check(hdlg, IDC_CHECK_MPU401, temp_mpu401);
                    settings_enable_window(hdlg, IDC_CHECK_MPU401, mpu401_standalone_allow());
                    settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_standalone_allow() && temp_mpu401);
                    break;

                case IDC_CONFIGURE_MIDI_OUT:
                    temp_midi_output_device = settings_list_to_midi[settings_get_cur_sel(hdlg, IDC_COMBO_MIDI_OUT)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) midi_out_device_getdevice(temp_midi_output_device));
                    break;

                case IDC_COMBO_MIDI_IN:
                    temp_midi_input_device = settings_list_to_midi_in[settings_get_cur_sel(hdlg, IDC_COMBO_MIDI_IN)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_MIDI_IN, midi_in_device_has_config(temp_midi_input_device));
                    settings_set_check(hdlg, IDC_CHECK_MPU401, temp_mpu401);
                    settings_enable_window(hdlg, IDC_CHECK_MPU401, mpu401_standalone_allow());
                    settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_standalone_allow() && temp_mpu401);
                    break;

                case IDC_CONFIGURE_MIDI_IN:
                    temp_midi_input_device = settings_list_to_midi_in[settings_get_cur_sel(hdlg, IDC_COMBO_MIDI_IN)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) midi_in_device_getdevice(temp_midi_input_device));
                    break;

                case IDC_CHECK_MPU401:
                    temp_mpu401 = settings_get_check(hdlg, IDC_CHECK_MPU401);

                    settings_enable_window(hdlg, IDC_CONFIGURE_MPU401, mpu401_present());
                    break;

                case IDC_CONFIGURE_MPU401:
                    temp_deviceconfig |= deviceconfig_open(hdlg, machine_has_bus(temp_machine, MACHINE_BUS_MCA) ? (void *) &mpu401_mca_device : (void *) &mpu401_device);
                    break;
            }
            return FALSE;

        case WM_SAVESETTINGS:
            temp_sound_card[0]      = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND1)];
            temp_sound_card[1]      = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND2)];
            temp_sound_card[2]      = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND3)];
            temp_sound_card[3]      = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SOUND4)];
            temp_midi_output_device = settings_list_to_midi[settings_get_cur_sel(hdlg, IDC_COMBO_MIDI_OUT)];
            temp_midi_input_device  = settings_list_to_midi_in[settings_get_cur_sel(hdlg, IDC_COMBO_MIDI_IN)];
            temp_mpu401             = settings_get_check(hdlg, IDC_CHECK_MPU401);
            temp_float              = settings_get_check(hdlg, IDC_CHECK_FLOAT);
            if (settings_get_check(hdlg, IDC_RADIO_FM_DRV_NUKED))
                temp_fm_driver = FM_DRV_NUKED;
            if (settings_get_check(hdlg, IDC_RADIO_FM_DRV_YMFM))
                temp_fm_driver = FM_DRV_YMFM;
        default:
            return FALSE;
    }
    return FALSE;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_ports_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    int         c;
    int         i;
    const char *s;
    LPTSTR      lptsTemp;

    switch (message) {
        case WM_INITDIALOG:
            lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

            for (i = 0; i < PARALLEL_MAX; i++) {
                c = 0;
                while (1) {
                    s = (char *) lpt_device_get_name(c);

                    if (!s)
                        break;

                    if (c == 0)
                        settings_add_string(hdlg, IDC_COMBO_LPT1 + i, win_get_string(IDS_2104));
                    else {
                        mbstowcs(lptsTemp, s, strlen(s) + 1);
                        settings_add_string(hdlg, IDC_COMBO_LPT1 + i, (LPARAM) lptsTemp);
                    }

                    c++;
                }
                settings_set_cur_sel(hdlg, IDC_COMBO_LPT1 + i, temp_lpt_devices[i]);

                settings_set_check(hdlg, IDC_CHECK_PARALLEL1 + i, temp_lpt[i]);
                settings_enable_window(hdlg, IDC_COMBO_LPT1 + i, temp_lpt[i]);
            }

            for (i = 0; i < SERIAL_MAX; i++) {
                settings_set_check(hdlg, IDC_CHECK_SERIAL1 + i, temp_serial[i]);
                settings_set_check(hdlg, IDC_CHECK_SERIAL_PASS1 + i, temp_serial_passthrough_enabled[i]);
            }

            free(lptsTemp);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_CHECK_PARALLEL1:
                case IDC_CHECK_PARALLEL2:
                case IDC_CHECK_PARALLEL3:
                case IDC_CHECK_PARALLEL4:
                    i = LOWORD(wParam) - IDC_CHECK_PARALLEL1;
                    settings_enable_window(hdlg, IDC_COMBO_LPT1 + i,
                                           settings_get_check(hdlg, IDC_CHECK_PARALLEL1 + i) == BST_CHECKED);
                    break;
            }
            break;

        case WM_SAVESETTINGS:
            for (i = 0; i < PARALLEL_MAX; i++) {
                temp_lpt_devices[i] = settings_get_cur_sel(hdlg, IDC_COMBO_LPT1 + i);
                temp_lpt[i]         = settings_get_check(hdlg, IDC_CHECK_PARALLEL1 + i);
            }

            for (i = 0; i < SERIAL_MAX; i++) {
                temp_serial[i]                     = settings_get_check(hdlg, IDC_CHECK_SERIAL1 + i);
                temp_serial_passthrough_enabled[i] = settings_get_check(hdlg, IDC_CHECK_SERIAL_PASS1 + i);
            }

        default:
            return FALSE;
    }
    return FALSE;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_storage_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    int             c;
    int             d;
    int             e;
    int             is_at;
    LPTSTR          lptsTemp;
    char           *stransi;
    const device_t *scsi_dev;
    const device_t *fdc_dev;
    const device_t *hdc_dev;

    switch (message) {
        case WM_INITDIALOG:
            lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));
            stransi  = (char *) malloc(512);

            /*HD controller config*/
            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_HDC);
            while (1) {
                /* Skip "internal" if machine doesn't have it. */
                if ((c == 1) && !machine_has_flags(temp_machine, MACHINE_HDC)) {
                    c++;
                    continue;
                }

                generate_device_name(hdc_get_device(c), hdc_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (hdc_available(c)) {
                    hdc_dev = hdc_get_device(c);

                    if (device_is_valid(hdc_dev, temp_machine)) {
                        if (c == 0)
                            settings_add_string(hdlg, IDC_COMBO_HDC, win_get_string(IDS_2104));
                        else if (c == 1)
                            settings_add_string(hdlg, IDC_COMBO_HDC, win_get_string(IDS_2119));
                        else
                            settings_add_string(hdlg, IDC_COMBO_HDC, (LPARAM) device_name);
                        settings_list_to_hdc[d] = c;
                        if ((c == 0) || (c == temp_hdc))
                            settings_set_cur_sel(hdlg, IDC_COMBO_HDC, d);
                        d++;
                    }
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_COMBO_HDC, d);
            settings_enable_window(hdlg, IDC_CONFIGURE_HDC, hdc_has_config(temp_hdc));

            /*FD controller config*/
            c = d = 0;
            settings_reset_content(hdlg, IDC_COMBO_FDC);
            while (1) {
                generate_device_name(fdc_card_getdevice(c), fdc_card_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (fdc_card_available(c)) {
                    fdc_dev = fdc_card_getdevice(c);

                    if (device_is_valid(fdc_dev, temp_machine)) {
                        if (c == 0)
                            settings_add_string(hdlg, IDC_COMBO_FDC, win_get_string(IDS_2119));
                        else
                            settings_add_string(hdlg, IDC_COMBO_FDC, (LPARAM) device_name);
                        settings_list_to_fdc[d] = c;
                        if ((c == 0) || (c == temp_fdc_card))
                            settings_set_cur_sel(hdlg, IDC_COMBO_FDC, d);
                        d++;
                    }
                }

                c++;
            }

            settings_enable_window(hdlg, IDC_COMBO_FDC, d);
            settings_enable_window(hdlg, IDC_CONFIGURE_FDC, fdc_card_has_config(temp_fdc_card));

            /*SCSI config*/
            c = d = 0;
            for (e = 0; e < SCSI_BUS_MAX; e++)
                settings_reset_content(hdlg, IDC_COMBO_SCSI_1 + e);
            while (1) {
                generate_device_name(scsi_card_getdevice(c), scsi_card_get_internal_name(c), 1);

                if (!device_name[0])
                    break;

                if (scsi_card_available(c)) {
                    scsi_dev = scsi_card_getdevice(c);

                    if (device_is_valid(scsi_dev, temp_machine)) {
                        for (e = 0; e < SCSI_BUS_MAX; e++) {
                            if (c == 0)
                                settings_add_string(hdlg, IDC_COMBO_SCSI_1 + e, win_get_string(IDS_2104));
                            else
                                settings_add_string(hdlg, IDC_COMBO_SCSI_1 + e, (LPARAM) device_name);

                            if ((c == 0) || (c == temp_scsi_card[e]))
                                settings_set_cur_sel(hdlg, IDC_COMBO_SCSI_1 + e, d);
                        }

                        settings_list_to_device[0][d] = c;
                        d++;
                    }
                }

                c++;
            }

            for (c = 0; c < SCSI_BUS_MAX; c++) {
                settings_enable_window(hdlg, IDC_COMBO_SCSI_1 + c, d);
                settings_enable_window(hdlg, IDC_CONFIGURE_SCSI_1 + c, scsi_card_has_config(temp_scsi_card[c]));
            }
            is_at = IS_AT(temp_machine);
            settings_enable_window(hdlg, IDC_CHECK_IDE_TER, is_at);
            settings_enable_window(hdlg, IDC_BUTTON_IDE_TER, is_at && temp_ide_ter);
            settings_enable_window(hdlg, IDC_CHECK_IDE_QUA, is_at);
            settings_enable_window(hdlg, IDC_BUTTON_IDE_QUA, is_at && temp_ide_qua);
            settings_enable_window(hdlg, IDC_CHECK_CASSETTE, machine_has_bus(temp_machine, MACHINE_BUS_CASSETTE));
            settings_set_check(hdlg, IDC_CHECK_IDE_TER, temp_ide_ter);
            settings_set_check(hdlg, IDC_CHECK_IDE_QUA, temp_ide_qua);
            settings_set_check(hdlg, IDC_CHECK_CASSETTE, (temp_cassette && machine_has_bus(temp_machine, MACHINE_BUS_CASSETTE)));

            free(stransi);
            free(lptsTemp);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_CONFIGURE_FDC:
                    temp_fdc_card = settings_list_to_fdc[settings_get_cur_sel(hdlg, IDC_COMBO_FDC)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) fdc_card_getdevice(temp_fdc_card));
                    break;

                case IDC_COMBO_FDC:
                    temp_fdc_card = settings_list_to_fdc[settings_get_cur_sel(hdlg, IDC_COMBO_FDC)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_FDC, fdc_card_has_config(temp_fdc_card));
                    break;

                case IDC_CONFIGURE_HDC:
                    temp_hdc = settings_list_to_hdc[settings_get_cur_sel(hdlg, IDC_COMBO_HDC)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) hdc_get_device(temp_hdc));
                    break;

                case IDC_COMBO_HDC:
                    temp_hdc = settings_list_to_hdc[settings_get_cur_sel(hdlg, IDC_COMBO_HDC)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_HDC, hdc_has_config(temp_hdc));
                    break;

                case IDC_CONFIGURE_SCSI_1 ... IDC_CONFIGURE_SCSI_4:
                    c                 = LOWORD(wParam) - IDC_CONFIGURE_SCSI_1;
                    temp_scsi_card[c] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SCSI_1 + c)];
                    temp_deviceconfig |= deviceconfig_inst_open(hdlg, (void *) scsi_card_getdevice(temp_scsi_card[c]), c + 1);
                    break;

                case IDC_COMBO_SCSI_1 ... IDC_COMBO_SCSI_4:
                    c                 = LOWORD(wParam) - IDC_COMBO_SCSI_1;
                    temp_scsi_card[c] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SCSI_1 + c)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_SCSI_1 + c, scsi_card_has_config(temp_scsi_card[c]));
                    break;

                case IDC_CHECK_IDE_TER:
                    temp_ide_ter = settings_get_check(hdlg, IDC_CHECK_IDE_TER);
                    settings_enable_window(hdlg, IDC_BUTTON_IDE_TER, temp_ide_ter);
                    break;

                case IDC_BUTTON_IDE_TER:
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) &ide_ter_device);
                    break;

                case IDC_CHECK_IDE_QUA:
                    temp_ide_qua = settings_get_check(hdlg, IDC_CHECK_IDE_QUA);
                    settings_enable_window(hdlg, IDC_BUTTON_IDE_QUA, temp_ide_qua);
                    break;

                case IDC_BUTTON_IDE_QUA:
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) &ide_qua_device);
                    break;
            }
            return FALSE;

        case WM_SAVESETTINGS:
            temp_hdc      = settings_list_to_hdc[settings_get_cur_sel(hdlg, IDC_COMBO_HDC)];
            temp_fdc_card = settings_list_to_fdc[settings_get_cur_sel(hdlg, IDC_COMBO_FDC)];
            for (c = 0; c < SCSI_BUS_MAX; c++)
                temp_scsi_card[c] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_SCSI_1 + c)];
            temp_ide_ter  = settings_get_check(hdlg, IDC_CHECK_IDE_TER);
            temp_ide_qua  = settings_get_check(hdlg, IDC_CHECK_IDE_QUA);
            temp_cassette = settings_get_check(hdlg, IDC_CHECK_CASSETTE);

        default:
            return FALSE;
    }
    return FALSE;
}

static void
network_recalc_combos(HWND hdlg)
{
    ignore_change = 1;

    for (uint8_t i = 0; i < NET_CARD_MAX; i++) {
        settings_enable_window(hdlg, IDC_COMBO_PCAP1 + i, temp_net_type[i] == NET_TYPE_PCAP);
        settings_enable_window(hdlg, IDC_COMBO_NET1 + i,
                               (temp_net_type[i] == NET_TYPE_SLIRP) || ((temp_net_type[i] == NET_TYPE_PCAP) && (network_dev_to_id(temp_pcap_dev[i]) > 0)));
        settings_enable_window(hdlg, IDC_CONFIGURE_NET1 + i, network_card_has_config(temp_net_card[i]) && ((temp_net_type[i] == NET_TYPE_SLIRP) || ((temp_net_type[i] == NET_TYPE_PCAP) && (network_dev_to_id(temp_pcap_dev[i]) > 0))));
    }

    ignore_change = 0;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_network_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    int    c;
    int    d;
    LPTSTR lptsTemp;

    switch (message) {
        case WM_INITDIALOG:
            lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

            for (uint8_t i = 0; i < NET_CARD_MAX; i++) {
                settings_add_string(hdlg, IDC_COMBO_NET1_TYPE + i, (LPARAM) L"Null Driver");
                settings_add_string(hdlg, IDC_COMBO_NET1_TYPE + i, (LPARAM) L"SLiRP");
                settings_add_string(hdlg, IDC_COMBO_NET1_TYPE + i, (LPARAM) L"PCap");
                settings_set_cur_sel(hdlg, IDC_COMBO_NET1_TYPE + i, temp_net_type[i]);
                settings_enable_window(hdlg, IDC_COMBO_PCAP1 + i, temp_net_type[i] == NET_TYPE_PCAP);

                for (c = 0; c < network_ndev; c++) {
                    mbstowcs(lptsTemp, network_devs[c].description, strlen(network_devs[c].description) + 1);
                    settings_add_string(hdlg, IDC_COMBO_PCAP1 + i, (LPARAM) lptsTemp);
                }
                settings_set_cur_sel(hdlg, IDC_COMBO_PCAP1 + i, network_dev_to_id(temp_pcap_dev[i]));

                /* NIC config */
                c = d = 0;
                settings_reset_content(hdlg, IDC_COMBO_NET1 + i);
                while (1) {
                    generate_device_name(network_card_getdevice(c), network_card_get_internal_name(c), 1);

                    if (device_name[0] == L'\0')
                        break;

                    if (network_card_available(c) && device_is_valid(network_card_getdevice(c), temp_machine)) {
                        if (c == 0)
                            settings_add_string(hdlg, IDC_COMBO_NET1 + i, win_get_string(IDS_2104));
                        else
                            settings_add_string(hdlg, IDC_COMBO_NET1 + i, (LPARAM) device_name);
                        settings_list_to_device[0][d] = c;
                        if ((c == 0) || (c == temp_net_card[i]))
                            settings_set_cur_sel(hdlg, IDC_COMBO_NET1 + i, d);
                        d++;
                    }

                    c++;
                }

                settings_enable_window(hdlg, IDC_COMBO_NET1 + i, d);
                network_recalc_combos(hdlg);
            }
            free(lptsTemp);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_COMBO_NET1_TYPE:
                    if (ignore_change)
                        return FALSE;

                    temp_net_type[0] = settings_get_cur_sel(hdlg, IDC_COMBO_NET1_TYPE);
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_NET2_TYPE:
                    if (ignore_change)
                        return FALSE;

                    temp_net_type[1] = settings_get_cur_sel(hdlg, IDC_COMBO_NET2_TYPE);
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_NET3_TYPE:
                    if (ignore_change)
                        return FALSE;

                    temp_net_type[2] = settings_get_cur_sel(hdlg, IDC_COMBO_NET3_TYPE);
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_NET4_TYPE:
                    if (ignore_change)
                        return FALSE;

                    temp_net_type[3] = settings_get_cur_sel(hdlg, IDC_COMBO_NET4_TYPE);
                    network_recalc_combos(hdlg);
                    break;

                case IDC_COMBO_PCAP1:
                    if (ignore_change)
                        return FALSE;

                    memset(temp_pcap_dev[0], '\0', sizeof(temp_pcap_dev[0]));
                    strcpy(temp_pcap_dev[0], network_devs[settings_get_cur_sel(hdlg, IDC_COMBO_PCAP1)].device);
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_PCAP2:
                    if (ignore_change)
                        return FALSE;

                    memset(temp_pcap_dev[1], '\0', sizeof(temp_pcap_dev[1]));
                    strcpy(temp_pcap_dev[1], network_devs[settings_get_cur_sel(hdlg, IDC_COMBO_PCAP2)].device);
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_PCAP3:
                    if (ignore_change)
                        return FALSE;

                    memset(temp_pcap_dev[2], '\0', sizeof(temp_pcap_dev[2]));
                    strcpy(temp_pcap_dev[2], network_devs[settings_get_cur_sel(hdlg, IDC_COMBO_PCAP3)].device);
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_PCAP4:
                    if (ignore_change)
                        return FALSE;

                    memset(temp_pcap_dev[3], '\0', sizeof(temp_pcap_dev[3]));
                    strcpy(temp_pcap_dev[3], network_devs[settings_get_cur_sel(hdlg, IDC_COMBO_PCAP4)].device);
                    network_recalc_combos(hdlg);
                    break;

                case IDC_COMBO_NET1:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[0] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET1)];
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_NET2:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[1] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET2)];
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_NET3:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[2] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET3)];
                    network_recalc_combos(hdlg);
                    break;
                case IDC_COMBO_NET4:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[3] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET4)];
                    network_recalc_combos(hdlg);
                    break;

                case IDC_CONFIGURE_NET1:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[0] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET1)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) network_card_getdevice(temp_net_card[0]));
                    break;
                case IDC_CONFIGURE_NET2:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[1] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET2)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) network_card_getdevice(temp_net_card[1]));
                    break;
                case IDC_CONFIGURE_NET3:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[2] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET3)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) network_card_getdevice(temp_net_card[2]));
                    break;
                case IDC_CONFIGURE_NET4:
                    if (ignore_change)
                        return FALSE;

                    temp_net_card[3] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET4)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) network_card_getdevice(temp_net_card[3]));
                    break;
            }
            return FALSE;

        case WM_SAVESETTINGS:
            for (uint8_t i = 0; i < NET_CARD_MAX; i++) {
                temp_net_type[i] = settings_get_cur_sel(hdlg, IDC_COMBO_NET1_TYPE + i);
                memset(temp_pcap_dev[i], '\0', sizeof(temp_pcap_dev[i]));
                strcpy(temp_pcap_dev[i], network_devs[settings_get_cur_sel(hdlg, IDC_COMBO_PCAP1 + i)].device);
                temp_net_card[i] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_NET1 + i)];
            }
        default:
            return FALSE;
    }

    return FALSE;
}

static void
normalize_hd_list(void)
{
    hard_disk_t ihdd[HDD_NUM];
    int         j = 0;

    memset(ihdd, 0x00, HDD_NUM * sizeof(hard_disk_t));

    for (uint8_t i = 0; i < HDD_NUM; i++) {
        if (temp_hdd[i].bus != HDD_BUS_DISABLED) {
            memcpy(&(ihdd[j]), &(temp_hdd[i]), sizeof(hard_disk_t));
            j++;
        }
    }

    memcpy(temp_hdd, ihdd, HDD_NUM * sizeof(hard_disk_t));
}

static int
get_selected_hard_disk(HWND hdlg)
{
    int  hard_disk = -1;
    int  j = 0;
    HWND h;

    if (hd_listview_items == 0)
        return 0;

    for (int i = 0; i < hd_listview_items; i++) {
        h = GetDlgItem(hdlg, IDC_LIST_HARD_DISKS);
        j = ListView_GetItemState(h, i, LVIS_SELECTED);
        if (j)
            hard_disk = i;
    }

    return hard_disk;
}

static void
add_locations(HWND hdlg)
{
    LPTSTR lptsTemp;
    int    i = 0;

    lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

    for (i = 0; i < 6; i++)
        settings_add_string(hdlg, IDC_COMBO_HD_BUS, win_get_string(IDS_4352 + i));

    for (i = 0; i < 2; i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4097), i >> 1, i & 1);
        settings_add_string(hdlg, IDC_COMBO_HD_CHANNEL, (LPARAM) lptsTemp);
    }

    for (i = 0; i < (SCSI_BUS_MAX * SCSI_ID_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4135), i >> 4, i & 15);
        settings_add_string(hdlg, IDC_COMBO_HD_ID, (LPARAM) lptsTemp);
    }

    for (i = 0; i < (IDE_BUS_MAX * IDE_CHAN_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4097), i >> 1, i & 1);
        settings_add_string(hdlg, IDC_COMBO_HD_CHANNEL_IDE, (LPARAM) lptsTemp);
    }

    free(lptsTemp);
}

static uint8_t
next_free_binary_channel(uint64_t *tracking)
{
    for (int64_t i = 0; i < 2; i++) {
        if (!(*tracking & (0xffLL << (i << 3LL))))
            return i;
    }

    return 2;
}

static uint8_t
next_free_ide_channel(void)
{
    for (int64_t i = 0; i < (IDE_BUS_MAX * IDE_CHAN_MAX); i++) {
        if (!(ide_tracking & (0xffLL << (i << 3LL))))
            return i;
    }

    return 7;
}

static void
next_free_scsi_id(uint8_t *id)
{
    for (int64_t i = 0; i < (SCSI_BUS_MAX * SCSI_ID_MAX); i++) {
        if (!(scsi_tracking[i >> 3] & (0xffLL << ((i & 0x07) << 3LL)))) {
            *id = i;
            return;
        }
    }

    *id = 6;
}

static void
recalc_location_controls(HWND hdlg, int is_add_dlg, int assign_id)
{
    int bus = 0;

    for (uint16_t i = IDT_CHANNEL; i <= IDT_ID; i++)
        settings_show_window(hdlg, i, FALSE);
    settings_show_window(hdlg, IDC_COMBO_HD_CHANNEL, FALSE);
    settings_show_window(hdlg, IDC_COMBO_HD_ID, FALSE);
    settings_show_window(hdlg, IDC_COMBO_HD_CHANNEL_IDE, FALSE);

    if ((hd_listview_items > 0) || is_add_dlg) {
        bus = settings_get_cur_sel(hdlg, IDC_COMBO_HD_BUS) + 1;

        switch (bus) {
            case HDD_BUS_MFM: /* MFM */
                settings_show_window(hdlg, IDT_CHANNEL, TRUE);
                settings_show_window(hdlg, IDC_COMBO_HD_CHANNEL, TRUE);

                if (assign_id)
                    temp_hdd[lv1_current_sel].mfm_channel = next_free_binary_channel(&mfm_tracking);
                settings_set_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL, is_add_dlg ? new_hdd.mfm_channel : temp_hdd[lv1_current_sel].mfm_channel);
                break;
            case HDD_BUS_XTA: /* XTA */
                settings_show_window(hdlg, IDT_CHANNEL, TRUE);
                settings_show_window(hdlg, IDC_COMBO_HD_CHANNEL, TRUE);

                if (assign_id)
                    temp_hdd[lv1_current_sel].xta_channel = next_free_binary_channel(&xta_tracking);
                settings_set_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL, is_add_dlg ? new_hdd.xta_channel : temp_hdd[lv1_current_sel].xta_channel);
                break;
            case HDD_BUS_ESDI: /* ESDI */
                settings_show_window(hdlg, IDT_CHANNEL, TRUE);
                settings_show_window(hdlg, IDC_COMBO_HD_CHANNEL, TRUE);

                if (assign_id)
                    temp_hdd[lv1_current_sel].esdi_channel = next_free_binary_channel(&esdi_tracking);
                settings_set_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL, is_add_dlg ? new_hdd.esdi_channel : temp_hdd[lv1_current_sel].esdi_channel);
                break;
            case HDD_BUS_IDE:   /* IDE */
            case HDD_BUS_ATAPI: /* ATAPI */
                settings_show_window(hdlg, IDT_CHANNEL, TRUE);
                settings_show_window(hdlg, IDC_COMBO_HD_CHANNEL_IDE, TRUE);

                if (assign_id)
                    temp_hdd[lv1_current_sel].ide_channel = next_free_ide_channel();
                settings_set_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL_IDE, is_add_dlg ? new_hdd.ide_channel : temp_hdd[lv1_current_sel].ide_channel);
                break;
            case HDD_BUS_SCSI: /* SCSI */
                settings_show_window(hdlg, IDT_ID, TRUE);
                settings_show_window(hdlg, IDT_LUN, TRUE);
                settings_show_window(hdlg, IDC_COMBO_HD_ID, TRUE);

                if (assign_id)
                    next_free_scsi_id((is_add_dlg ? &(new_hdd.scsi_id) : &(temp_hdd[lv1_current_sel].scsi_id)));
                settings_set_cur_sel(hdlg, IDC_COMBO_HD_ID, is_add_dlg ? new_hdd.scsi_id : temp_hdd[lv1_current_sel].scsi_id);
        }
    }

    settings_show_window(hdlg, IDT_BUS, (hd_listview_items != 0) || is_add_dlg);
    settings_show_window(hdlg, IDC_COMBO_HD_BUS, (hd_listview_items != 0) || is_add_dlg);
}

static int
bus_full(uint64_t *tracking, int count)
{
    int full = 0;

    switch (count) {
        default:
        case 2:
            full = (*tracking & 0xFF00LL);
            full = full && (*tracking & 0x00FFLL);
            break;
        case 8:
            full = (*tracking & 0xFF00000000000000LL);
            full = full && (*tracking & 0x00FF000000000000LL);
            full = full && (*tracking & 0x0000FF0000000000LL);
            full = full && (*tracking & 0x000000FF00000000LL);
            full = full && (*tracking & 0x00000000FF000000LL);
            full = full && (*tracking & 0x0000000000FF0000LL);
            full = full && (*tracking & 0x000000000000FF00LL);
            full = full && (*tracking & 0x00000000000000FFLL);
            break;
    }

    return full;
}

static void
recalc_next_free_id(HWND hdlg)
{
    int i;
    int enable_add = 0;
    int c_mfm      = 0;
    int c_esdi     = 0;
    int c_xta      = 0;
    int c_ide      = 0;
    int c_atapi    = 0;
    int c_scsi     = 0;

    next_free_id = -1;

    for (i = 0; i < HDD_NUM; i++) {
        if (temp_hdd[i].bus == HDD_BUS_MFM)
            c_mfm++;
        else if (temp_hdd[i].bus == HDD_BUS_ESDI)
            c_esdi++;
        else if (temp_hdd[i].bus == HDD_BUS_XTA)
            c_xta++;
        else if (temp_hdd[i].bus == HDD_BUS_IDE)
            c_ide++;
        else if (temp_hdd[i].bus == HDD_BUS_ATAPI)
            c_atapi++;
        else if (temp_hdd[i].bus == HDD_BUS_SCSI)
            c_scsi++;
    }

    for (i = 0; i < HDD_NUM; i++) {
        if (temp_hdd[i].bus == HDD_BUS_DISABLED) {
            next_free_id = i;
            break;
        }
    }

    enable_add = enable_add || (next_free_id >= 0);
    enable_add = enable_add && ((c_mfm < MFM_NUM) || (c_esdi < ESDI_NUM) || (c_xta < XTA_NUM) || (c_ide < IDE_NUM) || (c_ide < ATAPI_NUM) || (c_scsi < SCSI_NUM));
    enable_add = enable_add && (!bus_full(&mfm_tracking, 2) || !bus_full(&esdi_tracking, 2) || !bus_full(&xta_tracking, 2) || !bus_full(&ide_tracking, IDE_CHAN_MAX * IDE_BUS_MAX) ||
                 !bus_full(&(scsi_tracking[0]), 8) || !bus_full(&(scsi_tracking[1]), 8) || !bus_full(&(scsi_tracking[2]), 8) || !bus_full(&(scsi_tracking[3]), 8) ||
                 !bus_full(&(scsi_tracking[4]), 8) || !bus_full(&(scsi_tracking[5]), 8) || !bus_full(&(scsi_tracking[6]), 8) || !bus_full(&(scsi_tracking[7]), 8));

    settings_enable_window(hdlg, IDC_BUTTON_HDD_ADD_NEW, enable_add);
    settings_enable_window(hdlg, IDC_BUTTON_HDD_ADD, enable_add);
    settings_enable_window(hdlg, IDC_BUTTON_HDD_REMOVE,
                           (c_mfm != 0) || (c_esdi != 0) || (c_xta != 0) || (c_ide != 0) || (c_atapi != 0) || (c_scsi != 0));
}

static void
win_settings_hard_disks_update_item(HWND hdlg, int i, int column)
{
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_HARD_DISKS);
    LVITEM lvI;
    WCHAR  szText[256];

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    lvI.iSubItem = column;
    lvI.iItem    = i;

    if (column == 0) { /* Bus */
        switch (temp_hdd[i].bus) {
            case HDD_BUS_MFM:
                wsprintf(szText, plat_get_string(IDS_4608), temp_hdd[i].mfm_channel >> 1, temp_hdd[i].mfm_channel & 1);
                break;
            case HDD_BUS_XTA:
                wsprintf(szText, plat_get_string(IDS_4609), temp_hdd[i].xta_channel >> 1, temp_hdd[i].xta_channel & 1);
                break;
            case HDD_BUS_ESDI:
                wsprintf(szText, plat_get_string(IDS_4610), temp_hdd[i].esdi_channel >> 1, temp_hdd[i].esdi_channel & 1);
                break;
            case HDD_BUS_IDE:
                wsprintf(szText, plat_get_string(IDS_4611), temp_hdd[i].ide_channel >> 1, temp_hdd[i].ide_channel & 1);
                break;
            case HDD_BUS_ATAPI:
                wsprintf(szText, plat_get_string(IDS_4612), temp_hdd[i].ide_channel >> 1, temp_hdd[i].ide_channel & 1);
                break;
            case HDD_BUS_SCSI:
                wsprintf(szText, plat_get_string(IDS_4613), temp_hdd[i].scsi_id >> 4, temp_hdd[i].scsi_id & 15);
                break;
        }
        lvI.pszText = szText;
        lvI.iImage  = 0;
    } else if (column == 1) { /* File */
        if (!strnicmp(temp_hdd[i].fn, usr_path, strlen(usr_path)))
            mbstoc16s(szText, temp_hdd[i].fn + strlen(usr_path), sizeof_w(szText));
        else
            mbstoc16s(szText, temp_hdd[i].fn, sizeof_w(szText));
        lvI.pszText = szText;
        lvI.iImage  = 0;
    } else if (column == 2) { /* Cylinders */
        wsprintf(szText, plat_get_string(IDS_4098), temp_hdd[i].tracks);
        lvI.pszText = szText;
        lvI.iImage  = 0;
    } else if (column == 3) { /* Heads */
        wsprintf(szText, plat_get_string(IDS_4098), temp_hdd[i].hpc);
        lvI.pszText = szText;
        lvI.iImage  = 0;
    } else if (column == 4) { /* Sectors */
        wsprintf(szText, plat_get_string(IDS_4098), temp_hdd[i].spt);
        lvI.pszText = szText;
        lvI.iImage  = 0;
    } else if (column == 5) { /* Size (MB) */
        wsprintf(szText, plat_get_string(IDS_4098), (temp_hdd[i].tracks * temp_hdd[i].hpc * temp_hdd[i].spt) >> 11);
        lvI.pszText = szText;
        lvI.iImage  = 0;
    } else if (column == 6) { /* Speed (RPM) */
        mbstoc16s(szText, hdd_preset_getname(temp_hdd[i].speed_preset), sizeof_w(szText));
        lvI.pszText = szText;
        lvI.iImage  = 0;
    }

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;
}

static BOOL
win_settings_hard_disks_recalc_list(HWND hdlg)
{
    LVITEM lvI;
    int    j = 0;
    WCHAR  szText[256];
    WCHAR  usr_path_w[1024];
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_HARD_DISKS);

    mbstoc16s(usr_path_w, usr_path, sizeof_w(usr_path_w));

    hd_listview_items = 0;
    lv1_current_sel   = -1;

    ListView_DeleteAllItems(hwndList);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    for (uint8_t i = 0; i < HDD_NUM; i++) {
        if (temp_hdd[i].bus > 0) {
            hdc_id_to_listview_index[i] = j;
            lvI.iSubItem                = 0;

            /* Bus */
            switch (temp_hdd[i].bus) {
                case HDD_BUS_MFM:
                    wsprintf(szText, plat_get_string(IDS_4608), temp_hdd[i].mfm_channel >> 1, temp_hdd[i].mfm_channel & 1);
                    break;
                case HDD_BUS_XTA:
                    wsprintf(szText, plat_get_string(IDS_4609), temp_hdd[i].xta_channel >> 1, temp_hdd[i].xta_channel & 1);
                    break;
                case HDD_BUS_ESDI:
                    wsprintf(szText, plat_get_string(IDS_4610), temp_hdd[i].esdi_channel >> 1, temp_hdd[i].esdi_channel & 1);
                    break;
                case HDD_BUS_IDE:
                    wsprintf(szText, plat_get_string(IDS_4611), temp_hdd[i].ide_channel >> 1, temp_hdd[i].ide_channel & 1);
                    break;
                case HDD_BUS_ATAPI:
                    wsprintf(szText, plat_get_string(IDS_4612), temp_hdd[i].ide_channel >> 1, temp_hdd[i].ide_channel & 1);
                    break;
                case HDD_BUS_SCSI:
                    wsprintf(szText, plat_get_string(IDS_4613), temp_hdd[i].scsi_id >> 4, temp_hdd[i].scsi_id & 15);
                    break;
            }
            lvI.pszText = szText;
            lvI.iItem   = j;
            lvI.iImage  = 0;

            if (ListView_InsertItem(hwndList, &lvI) == -1)
                return FALSE;

            /* File */
            lvI.iSubItem = 1;
            if (!strnicmp(temp_hdd[i].fn, usr_path, strlen(usr_path)))
                mbstoc16s(szText, temp_hdd[i].fn + strlen(usr_path), sizeof_w(szText));
            else
                mbstoc16s(szText, temp_hdd[i].fn, sizeof_w(szText));
            lvI.pszText = szText;

            if (ListView_SetItem(hwndList, &lvI) == -1)
                return FALSE;

            /* Cylinders */
            lvI.iSubItem = 2;
            wsprintf(szText, plat_get_string(IDS_4098), temp_hdd[i].tracks);
            lvI.pszText = szText;

            if (ListView_SetItem(hwndList, &lvI) == -1)
                return FALSE;

            /* Heads */
            lvI.iSubItem = 3;
            wsprintf(szText, plat_get_string(IDS_4098), temp_hdd[i].hpc);
            lvI.pszText = szText;

            if (ListView_SetItem(hwndList, &lvI) == -1)
                return FALSE;

            /* Sectors */
            lvI.iSubItem = 4;
            wsprintf(szText, plat_get_string(IDS_4098), temp_hdd[i].spt);
            lvI.pszText = szText;

            if (ListView_SetItem(hwndList, &lvI) == -1)
                return FALSE;

            /* Size (MB) */
            lvI.iSubItem = 5;
            wsprintf(szText, plat_get_string(IDS_4098), (temp_hdd[i].tracks * temp_hdd[i].hpc * temp_hdd[i].spt) >> 11);
            lvI.pszText = szText;

            if (ListView_SetItem(hwndList, &lvI) == -1)
                return FALSE;

            /* Speed (RPM) */
            lvI.iSubItem = 6;
            mbstoc16s(szText, hdd_preset_getname(temp_hdd[i].speed_preset), sizeof_w(szText));
            lvI.pszText = szText;

            if (ListView_SetItem(hwndList, &lvI) == -1)
                return FALSE;

            j++;
        } else
            hdc_id_to_listview_index[i] = -1;
    }

    hd_listview_items = j;

    return TRUE;
}

#define C_COLUMNS_HARD_DISKS_BUS   104
#define C_COLUMNS_HARD_DISKS_FILE  254
#define C_COLUMNS_HARD_DISKS_CYLS  50
#define C_COLUMNS_HARD_DISKS_HEADS 26
#define C_COLUMNS_HARD_DISKS_SECT  32
#define C_COLUMNS_HARD_DISKS_SIZE  50
#define C_COLUMNS_HARD_DISKS_SPEED 100

static void
win_settings_hard_disks_resize_columns(HWND hdlg)
{
    /* Bus, File, Cylinders, Heads, Sectors, Size */
    int width[C_COLUMNS_HARD_DISKS] = {
                                        C_COLUMNS_HARD_DISKS_BUS,
                                        C_COLUMNS_HARD_DISKS_FILE,
                                        C_COLUMNS_HARD_DISKS_CYLS,
                                        C_COLUMNS_HARD_DISKS_HEADS,
                                        C_COLUMNS_HARD_DISKS_SECT,
                                        C_COLUMNS_HARD_DISKS_SIZE,
                                        C_COLUMNS_HARD_DISKS_SPEED
                                      };
    int  total    = 0;
    HWND hwndList = GetDlgItem(hdlg, IDC_LIST_HARD_DISKS);
    RECT r;

    GetWindowRect(hwndList, &r);
    for (int iCol = 0; iCol < (C_COLUMNS_HARD_DISKS - 1); iCol++) {
        width[iCol] = MulDiv(width[iCol], dpi, 96);
        total += width[iCol];
        ListView_SetColumnWidth(hwndList, iCol, MulDiv(width[iCol], dpi, 96));
    }
    width[C_COLUMNS_HARD_DISKS - 1] = (r.right - r.left) - 4 - total;
    ListView_SetColumnWidth(hwndList, C_COLUMNS_HARD_DISKS - 1, width[C_COLUMNS_HARD_DISKS - 1]);
}

static BOOL
win_settings_hard_disks_init_columns(HWND hdlg)
{
    LVCOLUMN lvc;
    HWND     hwndList = GetDlgItem(hdlg, IDC_LIST_HARD_DISKS);

    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

    for (int iCol = 0; iCol < C_COLUMNS_HARD_DISKS; iCol++) {
        lvc.iSubItem = iCol;
        lvc.pszText  = plat_get_string(IDS_BUS + iCol);

        switch (iCol) {
            case 0: /* Bus */
                lvc.cx  = C_COLUMNS_HARD_DISKS_BUS;
                lvc.fmt = LVCFMT_LEFT;
                break;
            case 1: /* File */
                lvc.cx  = C_COLUMNS_HARD_DISKS_FILE;
                lvc.fmt = LVCFMT_LEFT;
                break;
            case 2: /* Cylinders */
                lvc.cx  = C_COLUMNS_HARD_DISKS_CYLS;
                lvc.fmt = LVCFMT_RIGHT;
                break;
            case 3: /* Heads */
                lvc.cx  = C_COLUMNS_HARD_DISKS_HEADS;
                lvc.fmt = LVCFMT_RIGHT;
                break;
            case 4: /* Sectors */
                lvc.cx  = C_COLUMNS_HARD_DISKS_SECT;
                lvc.fmt = LVCFMT_RIGHT;
                break;
            case 5: /* Size (MB) */
                lvc.cx  = C_COLUMNS_HARD_DISKS_SIZE;
                lvc.fmt = LVCFMT_RIGHT;
                break;
            case 6: /* Speed (RPM) */
                lvc.cx  = C_COLUMNS_HARD_DISKS_SPEED;
                lvc.fmt = LVCFMT_RIGHT;
                break;
        }

        if (ListView_InsertColumn(hwndList, iCol, &lvc) == -1)
            return FALSE;
    }

    win_settings_hard_disks_resize_columns(hdlg);
    return TRUE;
}

static void
get_edit_box_contents(HWND hdlg, int id, uint32_t *val)
{
    HWND  h;
    WCHAR szText[256];
    char  stransi[256];

    h = GetDlgItem(hdlg, id);
    SendMessage(h, WM_GETTEXT, 255, (LPARAM) szText);
    wcstombs(stransi, szText, 256);
    sscanf(stransi, "%u", val);
}

static void
set_edit_box_contents(HWND hdlg, int id, uint32_t val)
{
    HWND  h;
    WCHAR szText[256];

    h = GetDlgItem(hdlg, id);
    wsprintf(szText, plat_get_string(IDS_2107), val);
    SendMessage(h, WM_SETTEXT, wcslen(szText), (LPARAM) szText);
}

static void
set_edit_box_text_contents(HWND hdlg, int id, WCHAR *text)
{
    HWND h = GetDlgItem(hdlg, id);
    SendMessage(h, WM_SETTEXT, wcslen(text), (LPARAM) text);
}

static void
get_edit_box_text_contents(HWND hdlg, int id, WCHAR *text_buffer, int buffer_size)
{
    HWND h = GetDlgItem(hdlg, id);
    SendMessage(h, WM_GETTEXT, (WPARAM) buffer_size, (LPARAM) text_buffer);
}

static int
hdconf_initialize_hdt_combo(HWND hdlg)
{
    uint64_t temp_size = 0;
    uint32_t size_mb   = 0;
    WCHAR    szText[256];

    selection = 127;

    for (uint8_t i = 0; i < 127; i++) {
        temp_size = ((uint64_t) hdd_table[i][0]) * hdd_table[i][1] * hdd_table[i][2];
        size_mb   = (uint32_t) (temp_size >> 11LL);
        wsprintf(szText, plat_get_string(IDS_2108), size_mb, hdd_table[i][0], hdd_table[i][1], hdd_table[i][2]);
        settings_add_string(hdlg, IDC_COMBO_HD_TYPE, (LPARAM) szText);
        if ((tracks == (int) hdd_table[i][0]) && (hpc == (int) hdd_table[i][1]) && (spt == (int) hdd_table[i][2]))
            selection = i;
    }
    settings_add_string(hdlg, IDC_COMBO_HD_TYPE, win_get_string(IDS_4100));
    settings_add_string(hdlg, IDC_COMBO_HD_TYPE, win_get_string(IDS_4101));
    settings_set_cur_sel(hdlg, IDC_COMBO_HD_TYPE, selection);
    return selection;
}

static void
recalc_selection(HWND hdlg)
{
    selection = 127;
    for (uint8_t i = 0; i < 127; i++) {
        if ((tracks == (int) hdd_table[i][0]) && (hpc == (int) hdd_table[i][1]) && (spt == (int) hdd_table[i][2]))
            selection = i;
    }
    if ((selection == 127) && (hpc == 16) && (spt == 63))
        selection = 128;
    settings_set_cur_sel(hdlg, IDC_COMBO_HD_TYPE, selection);
}

HWND vhd_progress_hdlg;

static void
vhd_progress_callback(uint32_t current_sector, UNUSED(uint32_t total_sectors))
{
    MSG  msg;
    HWND h = GetDlgItem(vhd_progress_hdlg, IDC_PBAR_IMG_CREATE);
    SendMessage(h, PBM_SETPOS, current_sector, (LPARAM) 0);
    while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

/* If the disk geometry requested in the 86Box GUI is not compatible with the internal VHD geometry,
 * we adjust it to the next-largest size that is compatible. On average, this will be a difference
 * of about 21 MB, and should only be necessary for VHDs larger than 31.5 GB, so should never be more
 * than a tenth of a percent change in size.
 */
static void
adjust_86box_geometry_for_vhd(MVHDGeom *_86box_geometry, MVHDGeom *vhd_geometry)
{
    if (_86box_geometry->cyl <= 65535) {
        vhd_geometry->cyl   = _86box_geometry->cyl;
        vhd_geometry->heads = _86box_geometry->heads;
        vhd_geometry->spt   = _86box_geometry->spt;
        return;
    }

    int desired_sectors = _86box_geometry->cyl * _86box_geometry->heads * _86box_geometry->spt;
    if (desired_sectors > 267321600)
        desired_sectors = 267321600;

    int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */
    if (remainder > 0)
        desired_sectors += (85680 - remainder);

    _86box_geometry->cyl   = desired_sectors / (16 * 63);
    _86box_geometry->heads = 16;
    _86box_geometry->spt   = 63;

    vhd_geometry->cyl   = desired_sectors / (16 * 255);
    vhd_geometry->heads = 16;
    vhd_geometry->spt   = 255;
}

static void
adjust_vhd_geometry_for_86box(MVHDGeom *vhd_geometry)
{
    if (vhd_geometry->spt <= 63)
        return;

    int desired_sectors = vhd_geometry->cyl * vhd_geometry->heads * vhd_geometry->spt;
    if (desired_sectors > 267321600)
        desired_sectors = 267321600;

    int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */
    if (remainder > 0)
        desired_sectors -= remainder;

    vhd_geometry->cyl   = desired_sectors / (16 * 63);
    vhd_geometry->heads = 16;
    vhd_geometry->spt   = 63;
}

static MVHDGeom
create_drive_vhd_fixed(char *filename, int cyl, int heads, int spt)
{
    MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt };
    MVHDGeom vhd_geometry;
    adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry);

    HWND h = GetDlgItem(vhd_progress_hdlg, IDC_PBAR_IMG_CREATE);
    settings_show_window(vhd_progress_hdlg, IDT_FILE_NAME, FALSE);
    settings_show_window(vhd_progress_hdlg, IDC_EDIT_HD_FILE_NAME, FALSE);
    settings_show_window(vhd_progress_hdlg, IDC_CFILE, FALSE);
    settings_show_window(vhd_progress_hdlg, IDC_PBAR_IMG_CREATE, TRUE);
    settings_enable_window(vhd_progress_hdlg, IDT_PROGRESS, TRUE);
    SendMessage(h, PBM_SETRANGE32, (WPARAM) 0, (LPARAM) vhd_geometry.cyl * vhd_geometry.heads * vhd_geometry.spt);
    SendMessage(h, PBM_SETPOS, (WPARAM) 0, (LPARAM) 0);

    int       vhd_error = 0;
    MVHDMeta *vhd       = mvhd_create_fixed(filename, vhd_geometry, &vhd_error, vhd_progress_callback);
    if (vhd == NULL) {
        _86box_geometry.cyl   = 0;
        _86box_geometry.heads = 0;
        _86box_geometry.spt   = 0;
    } else {
        mvhd_close(vhd);
    }

    return _86box_geometry;
}

static MVHDGeom
create_drive_vhd_dynamic(char *filename, int cyl, int heads, int spt, int blocksize)
{
    MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt };
    MVHDGeom vhd_geometry;
    adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry);
    int                 vhd_error = 0;
    MVHDCreationOptions options;
    options.block_size_in_sectors = blocksize;
    options.path                  = filename;
    options.size_in_bytes         = 0;
    options.geometry              = vhd_geometry;
    options.type                  = MVHD_TYPE_DYNAMIC;

    MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error);
    if (vhd == NULL) {
        _86box_geometry.cyl   = 0;
        _86box_geometry.heads = 0;
        _86box_geometry.spt   = 0;
    } else {
        mvhd_close(vhd);
    }

    return _86box_geometry;
}

static MVHDGeom
create_drive_vhd_diff(char *filename, char *parent_filename, int blocksize)
{
    int                 vhd_error = 0;
    MVHDCreationOptions options;
    options.block_size_in_sectors = blocksize;
    options.path                  = filename;
    options.parent_path           = parent_filename;
    options.type                  = MVHD_TYPE_DIFF;

    MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error);
    MVHDGeom  vhd_geometry;
    if (vhd == NULL) {
        vhd_geometry.cyl   = 0;
        vhd_geometry.heads = 0;
        vhd_geometry.spt   = 0;
    } else {
        vhd_geometry = mvhd_get_geometry(vhd);

        if (vhd_geometry.spt > 63) {
            vhd_geometry.cyl   = mvhd_calc_size_sectors(&vhd_geometry) / (16 * 63);
            vhd_geometry.heads = 16;
            vhd_geometry.spt   = 63;
        }

        mvhd_close(vhd);
    }

    return vhd_geometry;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_hard_disks_add_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    HWND     h;
    FILE    *fp;
    uint32_t temp;
    uint32_t i         = 0;
    uint32_t sector_size = 512;
    uint32_t zero      = 0;
    uint32_t base      = 0x1000;
    uint64_t signature = 0xD778A82044445459LL;
    uint64_t r         = 0;
    char    *big_buf;
    char     hd_file_name_multibyte[1200];
    int      b         = 0;
    int      vhd_error = 0;
    uint8_t  channel   = 0;
    uint8_t  id        = 0;
    wchar_t *twcs;
    int      img_format;
    int      block_size;
    WCHAR    text_buf[256];
    RECT     rect;
    POINT    point;
    int      dlg_height_adjust;

    switch (message) {
        case WM_INITDIALOG:
            memset(hd_file_name, 0, sizeof(hd_file_name));

            hdd_ptr = &(temp_hdd[next_free_id]);

            SetWindowText(hdlg, plat_get_string((existing & 1) ? IDS_4103 : IDS_4102));

            no_update = 1;
            spt       = (existing & 1) ? 0 : 17;
            set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, spt);
            hpc = (existing & 1) ? 0 : 15;
            set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, hpc);
            tracks = (existing & 1) ? 0 : 1023;
            set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, tracks);
            size = (tracks * hpc * spt) << 9;
            set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20LL));
            hdconf_initialize_hdt_combo(hdlg);

            settings_add_string(hdlg, IDC_COMBO_HD_IMG_FORMAT, win_get_string(IDS_4122));
            settings_add_string(hdlg, IDC_COMBO_HD_IMG_FORMAT, win_get_string(IDS_4123));
            settings_add_string(hdlg, IDC_COMBO_HD_IMG_FORMAT, win_get_string(IDS_4124));
            settings_add_string(hdlg, IDC_COMBO_HD_IMG_FORMAT, win_get_string(IDS_4125));
            settings_add_string(hdlg, IDC_COMBO_HD_IMG_FORMAT, win_get_string(IDS_4126));
            settings_add_string(hdlg, IDC_COMBO_HD_IMG_FORMAT, win_get_string(IDS_4127));
            settings_set_cur_sel(hdlg, IDC_COMBO_HD_IMG_FORMAT, 0);

            settings_add_string(hdlg, IDC_COMBO_HD_BLOCK_SIZE, win_get_string(IDS_4128));
            settings_add_string(hdlg, IDC_COMBO_HD_BLOCK_SIZE, win_get_string(IDS_4129));
            settings_set_cur_sel(hdlg, IDC_COMBO_HD_BLOCK_SIZE, 0);

            settings_show_window(hdlg, IDC_COMBO_HD_BLOCK_SIZE, FALSE);
            settings_show_window(hdlg, IDT_BLOCK_SIZE, FALSE);

            if (existing & 1) {
                settings_enable_window(hdlg, IDC_EDIT_HD_SPT, FALSE);
                settings_enable_window(hdlg, IDC_EDIT_HD_HPC, FALSE);
                settings_enable_window(hdlg, IDC_EDIT_HD_CYL, FALSE);
                settings_enable_window(hdlg, IDC_EDIT_HD_SIZE, FALSE);
                settings_enable_window(hdlg, IDC_COMBO_HD_TYPE, FALSE);
                settings_show_window(hdlg, IDC_COMBO_HD_IMG_FORMAT, FALSE);
                settings_show_window(hdlg, IDT_IMG_FORMAT, FALSE);

                /* adjust window size */
                GetWindowRect(hdlg, &rect);
                OffsetRect(&rect, -rect.left, -rect.top);
                dlg_height_adjust = rect.bottom / 5;
                SetWindowPos(hdlg, NULL, 0, 0, rect.right, rect.bottom - dlg_height_adjust, SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOZORDER);
                h = GetDlgItem(hdlg, IDOK);
                GetWindowRect(h, &rect);
                point.x = rect.left;
                point.y = rect.top;
                ScreenToClient(hdlg, &point);
                SetWindowPos(h, NULL, point.x, point.y - dlg_height_adjust, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_NOZORDER);
                h = GetDlgItem(hdlg, IDCANCEL);
                GetWindowRect(h, &rect);
                point.x = rect.left;
                point.y = rect.top;
                ScreenToClient(hdlg, &point);
                SetWindowPos(h, NULL, point.x, point.y - dlg_height_adjust, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_NOZORDER);

                chs_enabled = 0;
            } else
                chs_enabled = 1;

            add_locations(hdlg);
            hdd_ptr->bus = HDD_BUS_IDE;
            max_spt      = 255;
            max_hpc      = 255;
            settings_set_cur_sel(hdlg, IDC_COMBO_HD_BUS, hdd_ptr->bus - 1);
            max_tracks = 266305;
            recalc_location_controls(hdlg, 1, 0);

            channel = next_free_ide_channel();
            next_free_scsi_id(&id);
            settings_set_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL, 0);
            settings_set_cur_sel(hdlg, IDC_COMBO_HD_ID, id);
            settings_set_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL_IDE, channel);

            new_hdd.mfm_channel  = next_free_binary_channel(&mfm_tracking);
            new_hdd.esdi_channel = next_free_binary_channel(&esdi_tracking);
            new_hdd.xta_channel  = next_free_binary_channel(&xta_tracking);
            new_hdd.ide_channel  = channel;
            new_hdd.scsi_id      = id;

            settings_enable_window(hdlg, IDC_EDIT_HD_FILE_NAME, FALSE);
            settings_show_window(hdlg, IDT_PROGRESS, FALSE);
            settings_show_window(hdlg, IDC_PBAR_IMG_CREATE, FALSE);

            no_update = 0;
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    hdd_ptr->bus = settings_get_cur_sel(hdlg, IDC_COMBO_HD_BUS) + 1;

                    /* Make sure no file name is allowed with removable SCSI hard disks. */
                    if (wcslen(hd_file_name) == 0) {
                        hdd_ptr->bus = HDD_BUS_DISABLED;
                        settings_msgbox_header(MBX_ERROR, (wchar_t *) IDS_2131, (wchar_t *) IDS_4112);
                        return TRUE;
                    }

                    get_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, &(hdd_ptr->spt));
                    get_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, &(hdd_ptr->hpc));
                    get_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, &(hdd_ptr->tracks));
                    spt    = hdd_ptr->spt;
                    hpc    = hdd_ptr->hpc;
                    tracks = hdd_ptr->tracks;

                    switch (hdd_ptr->bus) {
                        case HDD_BUS_MFM:
                            hdd_ptr->mfm_channel = settings_get_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL);
                            break;
                        case HDD_BUS_ESDI:
                            hdd_ptr->esdi_channel = settings_get_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL);
                            break;
                        case HDD_BUS_XTA:
                            hdd_ptr->xta_channel = settings_get_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL);
                            break;
                        case HDD_BUS_IDE:
                        case HDD_BUS_ATAPI:
                            hdd_ptr->ide_channel = settings_get_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL_IDE);
                            break;
                        case HDD_BUS_SCSI:
                            hdd_ptr->scsi_id = settings_get_cur_sel(hdlg, IDC_COMBO_HD_ID);
                            break;
                    }

                    memset(hdd_ptr->fn, 0, sizeof(hdd_ptr->fn));
                    c16stombs(hdd_ptr->fn, hd_file_name, sizeof(hdd_ptr->fn));
                    strcpy(hd_file_name_multibyte, hdd_ptr->fn);

                    sector_size = 512;

                    if (!(existing & 1) && (wcslen(hd_file_name) > 0)) {
                        if (size > 0x1FFFFFFE00LL) {
                            settings_msgbox_header(MBX_ERROR, (wchar_t *) IDS_4116, (wchar_t *) IDS_4105);
                            return TRUE;
                        }

                        img_format = settings_get_cur_sel(hdlg, IDC_COMBO_HD_IMG_FORMAT);
                        if (img_format < IMG_FMT_VHD_FIXED) {
                            fp = _wfopen(hd_file_name, L"wb");
                        } else {
                            fp = (FILE *) 0;
                        }

                        if (img_format == IMG_FMT_HDI) { /* HDI file */
                            if (size >= 0x100000000LL) {
                                fclose(fp);
                                settings_msgbox_header(MBX_ERROR, (wchar_t *) IDS_4116, (wchar_t *) IDS_4104);
                                return TRUE;
                            }

                            fwrite(&zero, 1, 4, fp);        /* 00000000: Zero/unknown */
                            fwrite(&zero, 1, 4, fp);        /* 00000004: Zero/unknown */
                            fwrite(&base, 1, 4, fp);        /* 00000008: Offset at which data starts */
                            fwrite(&size, 1, 4, fp);        /* 0000000C: Full size of the data (32-bit) */
                            fwrite(&sector_size, 1, 4, fp); /* 00000010: Sector size in bytes */
                            fwrite(&spt, 1, 4, fp);         /* 00000014: Sectors per cylinder */
                            fwrite(&hpc, 1, 4, fp);         /* 00000018: Heads per cylinder */
                            fwrite(&tracks, 1, 4, fp);      /* 0000001C: Cylinders */

                            for (i = 0; i < 0x3f8; i++)
                                fwrite(&zero, 1, 4, fp);
                        } else if (img_format == IMG_FMT_HDX) {       /* HDX file */
                            fwrite(&signature, 1, 8, fp);              /* 00000000: Signature */
                            fwrite(&size, 1, 8, fp);                   /* 00000008: Full size of the data (64-bit) */
                            fwrite(&sector_size, 1, 4, fp);            /* 00000010: Sector size in bytes */
                            fwrite(&spt, 1, 4, fp);                    /* 00000014: Sectors per cylinder */
                            fwrite(&hpc, 1, 4, fp);                    /* 00000018: Heads per cylinder */
                            fwrite(&tracks, 1, 4, fp);                 /* 0000001C: Cylinders */
                            fwrite(&zero, 1, 4, fp);                   /* 00000020: [Translation] Sectors per cylinder */
                            fwrite(&zero, 1, 4, fp);                   /* 00000004: [Translation] Heads per cylinder */
                        } else if (img_format >= IMG_FMT_VHD_FIXED) { /* VHD file */
                            MVHDGeom _86box_geometry;
                            block_size = settings_get_cur_sel(hdlg, IDC_COMBO_HD_BLOCK_SIZE) == 0 ? MVHD_BLOCK_LARGE : MVHD_BLOCK_SMALL;
                            switch (img_format) {
                                case IMG_FMT_VHD_FIXED:
                                    vhd_progress_hdlg = hdlg;
                                    _86box_geometry   = create_drive_vhd_fixed(hd_file_name_multibyte, tracks, hpc, spt);
                                    break;
                                case IMG_FMT_VHD_DYNAMIC:
                                    _86box_geometry = create_drive_vhd_dynamic(hd_file_name_multibyte, tracks, hpc, spt, block_size);
                                    break;
                                case IMG_FMT_VHD_DIFF:
                                    if (file_dlg_w(hdlg, plat_get_string(IDS_4130), L"", plat_get_string(IDS_4131), 0)) {
                                        return TRUE;
                                    }
                                    _86box_geometry = create_drive_vhd_diff(hd_file_name_multibyte, openfilestring, block_size);
                                    break;
                            }

                            if (img_format != IMG_FMT_VHD_DIFF)
                                settings_msgbox_header(MBX_INFO, (wchar_t *) IDS_4113, (wchar_t *) IDS_4117);

                            hdd_ptr->tracks = _86box_geometry.cyl;
                            hdd_ptr->hpc    = _86box_geometry.heads;
                            hdd_ptr->spt    = _86box_geometry.spt;

                            hard_disk_added = 1;
                            EndDialog(hdlg, 0);
                            return TRUE;
                        }

                        big_buf = (char *) malloc(1048576);
                        memset(big_buf, 0, 1048576);

                        r = size >> 20;
                        size &= 0xfffff;

                        if (size || r) {
                            settings_show_window(hdlg, IDT_FILE_NAME, FALSE);
                            settings_show_window(hdlg, IDC_EDIT_HD_FILE_NAME, FALSE);
                            settings_show_window(hdlg, IDC_CFILE, FALSE);
                            settings_show_window(hdlg, IDC_PBAR_IMG_CREATE, TRUE);
                            settings_enable_window(hdlg, IDT_PROGRESS, TRUE);

                            h = GetDlgItem(hdlg, IDC_PBAR_IMG_CREATE);
                            SendMessage(h, PBM_SETRANGE32, (WPARAM) 0, (LPARAM) r);
                            SendMessage(h, PBM_SETPOS, (WPARAM) 0, (LPARAM) 0);
                        }

                        h = GetDlgItem(hdlg, IDC_PBAR_IMG_CREATE);

                        if (size) {
                            if (fp) {
                                fwrite(big_buf, 1, size, fp);
                            }
                            SendMessage(h, PBM_SETPOS, (WPARAM) 1, (LPARAM) 0);
                        }

                        if (r) {
                            for (i = 0; i < r; i++) {
                                if (fp) {
                                    fwrite(big_buf, 1, 1048576, fp);
                                }
                                SendMessage(h, PBM_SETPOS, (i + 1), (LPARAM) 0);

                                settings_process_messages();
                            }
                        }

                        free(big_buf);

                        if (fp) {
                            fclose(fp);
                        }
                        settings_msgbox_header(MBX_INFO, (wchar_t *) IDS_4113, (wchar_t *) IDS_4117);
                    }

                    hard_disk_added = 1;
                    EndDialog(hdlg, 0);
                    return TRUE;

                case IDCANCEL:
                    hard_disk_added = 0;
                    hdd_ptr->bus    = HDD_BUS_DISABLED;
                    EndDialog(hdlg, 0);
                    return TRUE;

                case IDC_CFILE:
                    if (!file_dlg_w(hdlg, plat_get_string(IDS_4106), L"", NULL, !(existing & 1))) {
                        if (!wcschr(wopenfilestring, L'.')) {
                            if (wcslen(wopenfilestring) && (wcslen(wopenfilestring) <= 256)) {
                                twcs    = &wopenfilestring[wcslen(wopenfilestring)];
                                twcs[0] = L'.';
                                twcs[1] = L'i';
                                twcs[2] = L'm';
                                twcs[3] = L'g';
                            }
                        }

                        if (!(existing & 1)) {
                            fp = _wfopen(wopenfilestring, L"rb");
                            if (fp != NULL) {
                                fclose(fp);
                                if (settings_msgbox_ex(MBX_QUESTION_YN, (wchar_t *) IDS_4111, (wchar_t *) IDS_4118, (wchar_t *) IDS_4120, (wchar_t *) IDS_4121, NULL) != 0) /* yes */
                                    return FALSE;
                            }
                        }

                        fp = _wfopen(wopenfilestring, (existing & 1) ? L"rb" : L"wb");
                        if (fp == NULL) {
hdd_add_file_open_error:
                            fclose(fp);
                            settings_msgbox_header(MBX_ERROR, (existing & 1) ? (wchar_t *) IDS_4114 : (wchar_t *) IDS_4115, (existing & 1) ? (wchar_t *) IDS_4107 : (wchar_t *) IDS_4108);
                            return TRUE;
                        }
                        if (existing & 1) {
                            if (image_is_hdi(openfilestring) || image_is_hdx(openfilestring, 1)) {
                                fseeko64(fp, 0x10, SEEK_SET);
                                fread(&sector_size, 1, 4, fp);
                                if (sector_size != 512) {
                                    settings_msgbox_header(MBX_ERROR, (wchar_t *) IDS_4119, (wchar_t *) IDS_4109);
                                    fclose(fp);
                                    return TRUE;
                                }
                                spt = hpc = tracks = 0;
                                fread(&spt, 1, 4, fp);
                                fread(&hpc, 1, 4, fp);
                                fread(&tracks, 1, 4, fp);
                            } else if (image_is_vhd(openfilestring, 1)) {
                                fclose(fp);
                                MVHDMeta *vhd = mvhd_open(openfilestring, 0, &vhd_error);
                                if (vhd == NULL) {
                                    settings_msgbox_header(MBX_ERROR, (existing & 1) ? (wchar_t *) IDS_4114 : (wchar_t *) IDS_4115, (existing & 1) ? (wchar_t *) IDS_4107 : (wchar_t *) IDS_4108);
                                    return TRUE;
                                } else if (vhd_error == MVHD_ERR_TIMESTAMP) {
                                    if (settings_msgbox_ex(MBX_QUESTION_YN | MBX_WARNING, plat_get_string(IDS_4133), plat_get_string(IDS_4132), NULL, NULL, NULL) != 0) {
                                        int ts_res = mvhd_diff_update_par_timestamp(vhd, &vhd_error);
                                        if (ts_res != 0) {
                                            settings_msgbox_header(MBX_ERROR, plat_get_string(IDS_2049), plat_get_string(IDS_4134));
                                            mvhd_close(vhd);
                                            return TRUE;
                                        }
                                    } else {
                                        mvhd_close(vhd);
                                        return TRUE;
                                    }
                                }

                                MVHDGeom vhd_geom = mvhd_get_geometry(vhd);
                                adjust_vhd_geometry_for_86box(&vhd_geom);
                                tracks = vhd_geom.cyl;
                                hpc    = vhd_geom.heads;
                                spt    = vhd_geom.spt;
                                size   = (uint64_t) tracks * hpc * spt * 512;
                                mvhd_close(vhd);
                            } else {
                                fseeko64(fp, 0, SEEK_END);
                                size = ftello64(fp);
                                if (((size % 17) == 0) && (size <= 142606336)) {
                                    spt = 17;
                                    if (size <= 26738688)
                                        hpc = 4;
                                    else if (((size % 3072) == 0) && (size <= 53477376))
                                        hpc = 6;
                                    else {
                                        for (i = 5; i < 16; i++) {
                                            if (((size % (i << 9)) == 0) && (size <= ((i * 17) << 19)))
                                                break;
                                            if (i == 5)
                                                i++;
                                        }
                                        hpc = i;
                                    }
                                } else {
                                    spt = 63;
                                    hpc = 16;
                                }

                                tracks = ((size >> 9) / hpc) / spt;
                            }

                            if ((spt > max_spt) || (hpc > max_hpc) || (tracks > max_tracks))
                                goto hdd_add_file_open_error;
                            no_update = 1;

                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, spt);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, hpc);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, tracks);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, size >> 20);
                            recalc_selection(hdlg);

                            settings_enable_window(hdlg, IDC_EDIT_HD_SPT, TRUE);
                            settings_enable_window(hdlg, IDC_EDIT_HD_HPC, TRUE);
                            settings_enable_window(hdlg, IDC_EDIT_HD_CYL, TRUE);
                            settings_enable_window(hdlg, IDC_EDIT_HD_SIZE, TRUE);
                            settings_enable_window(hdlg, IDC_COMBO_HD_TYPE, TRUE);

                            chs_enabled = 1;

                            no_update = 0;
                        }

                        fclose(fp);
                    }

                    h = GetDlgItem(hdlg, IDC_EDIT_HD_FILE_NAME);
                    SendMessage(h, WM_SETTEXT, 0, (LPARAM) wopenfilestring);
                    memset(hd_file_name, 0, sizeof(hd_file_name));
                    wcscpy(hd_file_name, wopenfilestring);

                    return TRUE;

                case IDC_EDIT_HD_CYL:
                    if (no_update)
                        return FALSE;

                    no_update = 1;
                    get_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, &temp);
                    if (tracks != (int64_t) temp) {
                        tracks = temp;
                        size   = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    if (tracks > max_tracks) {
                        tracks = max_tracks;
                        size   = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, (uint32_t) tracks);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    no_update = 0;
                    break;

                case IDC_EDIT_HD_HPC:
                    if (no_update)
                        return FALSE;

                    no_update = 1;
                    get_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, &temp);
                    if (hpc != (int64_t) temp) {
                        hpc  = temp;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    if (hpc > max_hpc) {
                        hpc  = max_hpc;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, (uint32_t) hpc);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    no_update = 0;
                    break;

                case IDC_EDIT_HD_SPT:
                    if (no_update)
                        return FALSE;

                    no_update = 1;
                    get_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, &temp);
                    if (spt != (int64_t) temp) {
                        spt  = temp;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    if (spt > max_spt) {
                        spt  = max_spt;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, spt);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    no_update = 0;
                    break;

                case IDC_EDIT_HD_SIZE:
                    if (no_update)
                        return FALSE;

                    no_update = 1;
                    get_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, &temp);
                    if (temp != (uint32_t) (size >> 20)) {
                        size = ((uint64_t) temp) << 20LL;
                        /* This is needed to ensure VHD standard compliance. */
                        hdd_image_calc_chs((uint32_t *) &tracks, (uint32_t *) &hpc, (uint32_t *) &spt, temp);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, (uint32_t) tracks);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, (uint32_t) hpc);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, (uint32_t) spt);
                        recalc_selection(hdlg);
                    }

                    if (tracks > max_tracks) {
                        tracks = max_tracks;
                        size   = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, (uint32_t) tracks);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    if (hpc > max_hpc) {
                        hpc  = max_hpc;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, (uint32_t) hpc);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    if (spt > max_spt) {
                        spt  = max_spt;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, (uint32_t) spt);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    no_update = 0;
                    break;

                case IDC_COMBO_HD_TYPE:
                    if (no_update)
                        return FALSE;

                    no_update = 1;
                    temp      = settings_get_cur_sel(hdlg, IDC_COMBO_HD_TYPE);
                    if ((temp != selection) && (temp != 127) && (temp != 128)) {
                        selection = temp;
                        tracks    = hdd_table[selection][0];
                        hpc       = hdd_table[selection][1];
                        spt       = hdd_table[selection][2];
                        size      = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, (uint32_t) tracks);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, (uint32_t) hpc);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, (uint32_t) spt);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                    } else if ((temp != selection) && (temp == 127))
                        selection = temp;
                    else if ((temp != selection) && (temp == 128)) {
                        selection = temp;
                        hpc       = 16;
                        spt       = 63;
                        size      = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, (uint32_t) hpc);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, (uint32_t) spt);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                    }

                    if (spt > max_spt) {
                        spt  = max_spt;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, (uint32_t) spt);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    if (hpc > max_hpc) {
                        hpc  = max_hpc;
                        size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, (uint32_t) hpc);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    if (tracks > max_tracks) {
                        tracks = max_tracks;
                        size   = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, (uint32_t) tracks);
                        set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                        recalc_selection(hdlg);
                    }

                    no_update = 0;
                    break;

                case IDC_COMBO_HD_BUS:
                    if (no_update)
                        return FALSE;

                    no_update = 1;
                    recalc_location_controls(hdlg, 1, 0);
                    b = settings_get_cur_sel(hdlg, IDC_COMBO_HD_BUS) + 1;
                    if (b != hdd_ptr->bus) {
                        hdd_ptr->bus = b;

                        switch (hdd_ptr->bus) {
                            default:
                            case HDD_BUS_DISABLED:
                                max_spt = max_hpc = max_tracks = 0;
                                break;
                            case HDD_BUS_MFM:
                                max_spt    = 26; /* 17 for MFM, 26 for RLL. */
                                max_hpc    = 15;
                                max_tracks = 2047;
                                break;
                            case HDD_BUS_XTA:
                                max_spt    = 63;
                                max_hpc    = 16;
                                max_tracks = 1023;
                                break;
                            case HDD_BUS_ESDI:
                                max_spt    = 99; /* ESDI drives usually had 32 to 43 sectors per track. */
                                max_hpc    = 16;
                                max_tracks = 266305;
                                break;
                            case HDD_BUS_IDE:
                                max_spt    = 255;
                                max_hpc    = 255;
                                max_tracks = 266305;
                                break;
                            case HDD_BUS_ATAPI:
                            case HDD_BUS_SCSI:
                                max_spt    = 255;
                                max_hpc    = 255;
                                max_tracks = 266305;
                                break;
                        }

                        if (!chs_enabled) {
                            settings_enable_window(hdlg, IDC_EDIT_HD_SPT, FALSE);
                            settings_enable_window(hdlg, IDC_EDIT_HD_HPC, FALSE);
                            settings_enable_window(hdlg, IDC_EDIT_HD_CYL, FALSE);
                            settings_enable_window(hdlg, IDC_EDIT_HD_SIZE, FALSE);
                            settings_enable_window(hdlg, IDC_COMBO_HD_TYPE, FALSE);
                        }

                        if (spt > max_spt) {
                            spt  = max_spt;
                            size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, (uint32_t) spt);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                            recalc_selection(hdlg);
                        }

                        if (hpc > max_hpc) {
                            hpc  = max_hpc;
                            size = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, (uint32_t) hpc);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                            recalc_selection(hdlg);
                        }

                        if (tracks > max_tracks) {
                            tracks = max_tracks;
                            size   = ((uint64_t) tracks * (uint64_t) hpc * (uint64_t) spt) << 9LL;
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, (uint32_t) tracks);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) (size >> 20));
                            recalc_selection(hdlg);
                        }
                    }

                    no_update = 0;
                    break;
                case IDC_COMBO_HD_IMG_FORMAT:
                    img_format = settings_get_cur_sel(hdlg, IDC_COMBO_HD_IMG_FORMAT);

                    no_update = 1;
                    if (img_format == IMG_FMT_VHD_DIFF) { /* They switched to a diff VHD; disable the geometry fields. */
                        settings_enable_window(hdlg, IDC_EDIT_HD_SPT, FALSE);
                        set_edit_box_text_contents(hdlg, IDC_EDIT_HD_SPT, L"(N/A)");
                        settings_enable_window(hdlg, IDC_EDIT_HD_HPC, FALSE);
                        set_edit_box_text_contents(hdlg, IDC_EDIT_HD_HPC, L"(N/A)");
                        settings_enable_window(hdlg, IDC_EDIT_HD_CYL, FALSE);
                        set_edit_box_text_contents(hdlg, IDC_EDIT_HD_CYL, L"(N/A)");
                        settings_enable_window(hdlg, IDC_EDIT_HD_SIZE, FALSE);
                        set_edit_box_text_contents(hdlg, IDC_EDIT_HD_SIZE, L"(N/A)");
                        settings_enable_window(hdlg, IDC_COMBO_HD_TYPE, FALSE);
                        settings_reset_content(hdlg, IDC_COMBO_HD_TYPE);
                        settings_add_string(hdlg, IDC_COMBO_HD_TYPE, (LPARAM) L"(use parent)");
                        settings_set_cur_sel(hdlg, IDC_COMBO_HD_TYPE, 0);
                    } else {
                        get_edit_box_text_contents(hdlg, IDC_EDIT_HD_SPT, text_buf, 256);
                        if (!wcscmp(text_buf, L"(N/A)")) {
                            settings_enable_window(hdlg, IDC_EDIT_HD_SPT, TRUE);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SPT, 17);
                            spt = 17;
                            settings_enable_window(hdlg, IDC_EDIT_HD_HPC, TRUE);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_HPC, 15);
                            hpc = 15;
                            settings_enable_window(hdlg, IDC_EDIT_HD_CYL, TRUE);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_CYL, 1023);
                            tracks = 1023;
                            settings_enable_window(hdlg, IDC_EDIT_HD_SIZE, TRUE);
                            set_edit_box_contents(hdlg, IDC_EDIT_HD_SIZE, (uint32_t) ((uint64_t) 17 * 15 * 1023 * 512 >> 20));
                            size = (uint64_t) 17 * 15 * 1023 * 512;

                            settings_reset_content(hdlg, IDC_COMBO_HD_TYPE);
                            hdconf_initialize_hdt_combo(hdlg);
                            settings_enable_window(hdlg, IDC_COMBO_HD_TYPE, TRUE);
                        }
                    }
                    no_update = 0;

                    if (img_format == IMG_FMT_VHD_DYNAMIC || img_format == IMG_FMT_VHD_DIFF) { /* For dynamic and diff VHDs, show the block size dropdown. */
                        settings_show_window(hdlg, IDC_COMBO_HD_BLOCK_SIZE, TRUE);
                        settings_show_window(hdlg, IDT_BLOCK_SIZE, TRUE);
                    } else { /* Hide it otherwise. */
                        settings_show_window(hdlg, IDC_COMBO_HD_BLOCK_SIZE, FALSE);
                        settings_show_window(hdlg, IDT_BLOCK_SIZE, FALSE);
                    }
                    break;
            }

            return FALSE;
    }

    return FALSE;
}

int
hard_disk_was_added(void)
{
    return hard_disk_added;
}

void
hard_disk_add_open(HWND hwnd, int is_existing)
{
    existing        = is_existing;
    hard_disk_added = 0;
    DialogBox(hinstance, (LPCWSTR) DLG_CFG_HARD_DISKS_ADD, hwnd, win_settings_hard_disks_add_proc);
}

static void
hard_disk_track(uint8_t id)
{
    switch (temp_hdd[id].bus) {
        case HDD_BUS_MFM:
            mfm_tracking |= (1 << (temp_hdd[id].mfm_channel << 3));
            break;
        case HDD_BUS_ESDI:
            esdi_tracking |= (1 << (temp_hdd[id].esdi_channel << 3));
            break;
        case HDD_BUS_XTA:
            xta_tracking |= (1 << (temp_hdd[id].xta_channel << 3));
            break;
        case HDD_BUS_IDE:
        case HDD_BUS_ATAPI:
            ide_tracking |= (1 << (temp_hdd[id].ide_channel << 3));
            break;
        case HDD_BUS_SCSI:
            scsi_tracking[temp_hdd[id].scsi_id >> 3] |= (1 << ((temp_hdd[id].scsi_id & 0x07) << 3));
            break;
    }
}

static void
hard_disk_untrack(uint8_t id)
{
    switch (temp_hdd[id].bus) {
        case HDD_BUS_MFM:
            mfm_tracking &= ~(1 << (temp_hdd[id].mfm_channel << 3));
            break;
        case HDD_BUS_ESDI:
            esdi_tracking &= ~(1 << (temp_hdd[id].esdi_channel << 3));
            break;
        case HDD_BUS_XTA:
            xta_tracking &= ~(1 << (temp_hdd[id].xta_channel << 3));
            break;
        case HDD_BUS_IDE:
        case HDD_BUS_ATAPI:
            ide_tracking &= ~(1 << (temp_hdd[id].ide_channel << 3));
            break;
        case HDD_BUS_SCSI:
            scsi_tracking[temp_hdd[id].scsi_id >> 3] &= ~(1 << ((temp_hdd[id].scsi_id & 0x07) << 3));
            break;
    }
}

static void
hard_disk_track_all(void)
{
    for (uint8_t i = 0; i < HDD_NUM; i++)
        hard_disk_track(i);
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_hard_disks_proc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    int           old_sel     = 0;
    int           b           = 0;
    int           assign      = 0;
    const uint8_t hd_icons[2] = { 80, 0 };

    switch (message) {
        case WM_INITDIALOG:
            ignore_change = 1;

            normalize_hd_list(); /* Normalize the hard disks so that non-disabled hard disks start from index 0, and so they are contiguous.
                                    This will cause an emulator reset prompt on the first opening of this category with a messy hard disk list
                                    (which can only happen by manually editing the configuration file). */
            win_settings_hard_disks_init_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_HARD_DISKS, (const uint8_t *) hd_icons);
            win_settings_hard_disks_recalc_list(hdlg);
            recalc_next_free_id(hdlg);
            add_locations(hdlg);
            if (hd_listview_items > 0) {
                settings_listview_select(hdlg, IDC_LIST_HARD_DISKS, 0);
                lv1_current_sel = 0;
                settings_set_cur_sel(hdlg, IDC_COMBO_HD_BUS, temp_hdd[0].bus - 1);
            } else
                lv1_current_sel = -1;
            recalc_location_controls(hdlg, 0, 0);

            settings_listview_enable_styles(hdlg, IDC_LIST_HARD_DISKS);

            ignore_change = 0;
            return TRUE;

        case WM_NOTIFY:
            if ((hd_listview_items == 0) || ignore_change)
                return FALSE;

            if ((((LPNMHDR) lParam)->code == LVN_ITEMCHANGED) && (((LPNMHDR) lParam)->idFrom == IDC_LIST_HARD_DISKS)) {
                old_sel         = lv1_current_sel;
                lv1_current_sel = get_selected_hard_disk(hdlg);
                if (lv1_current_sel == old_sel)
                    return FALSE;
                ignore_change = 1;
                settings_set_cur_sel(hdlg, IDC_COMBO_HD_BUS, temp_hdd[lv1_current_sel].bus - 1);
                recalc_location_controls(hdlg, 0, 0);
                ignore_change = 0;
            }
            break;

        case WM_COMMAND:
            if (ignore_change && (LOWORD(wParam) != IDC_BUTTON_HDD_ADD) && (LOWORD(wParam) != IDC_BUTTON_HDD_ADD_NEW) && (LOWORD(wParam) != IDC_BUTTON_HDD_REMOVE))
                return FALSE;
            switch (LOWORD(wParam)) {
                case IDC_COMBO_HD_BUS:
                    ignore_change = 1;
                    b             = settings_get_cur_sel(hdlg, IDC_COMBO_HD_BUS) + 1;
                    if (b != temp_hdd[lv1_current_sel].bus) {
                        hard_disk_untrack(lv1_current_sel);
                        assign                        = (temp_hdd[lv1_current_sel].bus == b) ? 0 : 1;
                        temp_hdd[lv1_current_sel].bus = b;
                        recalc_location_controls(hdlg, 0, assign);
                        hard_disk_track(lv1_current_sel);
                        win_settings_hard_disks_update_item(hdlg, lv1_current_sel, 0);
                    }
                    ignore_change = 0;
                    return FALSE;

                case IDC_COMBO_HD_CHANNEL:
                    ignore_change = 1;
                    hard_disk_untrack(lv1_current_sel);
                    temp_hdd[lv1_current_sel].channel = settings_get_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL);
                    hard_disk_track(lv1_current_sel);
                    win_settings_hard_disks_update_item(hdlg, lv1_current_sel, 0);
                    ignore_change = 0;
                    return FALSE;

                case IDC_COMBO_HD_CHANNEL_IDE:
                    ignore_change = 1;
                    hard_disk_untrack(lv1_current_sel);
                    temp_hdd[lv1_current_sel].ide_channel = settings_get_cur_sel(hdlg, IDC_COMBO_HD_CHANNEL_IDE);
                    hard_disk_track(lv1_current_sel);
                    win_settings_hard_disks_update_item(hdlg, lv1_current_sel, 0);
                    ignore_change = 0;
                    return FALSE;

                case IDC_COMBO_HD_ID:
                    ignore_change = 1;
                    hard_disk_untrack(lv1_current_sel);
                    temp_hdd[lv1_current_sel].scsi_id = settings_get_cur_sel(hdlg, IDC_COMBO_HD_ID);
                    hard_disk_track(lv1_current_sel);
                    win_settings_hard_disks_update_item(hdlg, lv1_current_sel, 0);
                    ignore_change = 0;
                    return FALSE;

                case IDC_BUTTON_HDD_ADD:
                case IDC_BUTTON_HDD_ADD_NEW:
                    hard_disk_add_open(hdlg, (LOWORD(wParam) == IDC_BUTTON_HDD_ADD));
                    if (hard_disk_added) {
                        ignore_change = 1;
                        win_settings_hard_disks_recalc_list(hdlg);
                        recalc_next_free_id(hdlg);
                        hard_disk_track_all();
                        ignore_change = 0;
                    }
                    return FALSE;

                case IDC_BUTTON_HDD_REMOVE:
                    temp_hdd[lv1_current_sel].fn[0] = '\0';
                    hard_disk_untrack(lv1_current_sel);
                    temp_hdd[lv1_current_sel].bus = HDD_BUS_DISABLED; /* Only set the bus to zero, the list normalize code below will take care of turning this entire entry to a complete zero. */
                    normalize_hd_list();                              /* Normalize the hard disks so that non-disabled hard disks start from index 0, and so they are contiguous. */
                    ignore_change = 1;
                    win_settings_hard_disks_recalc_list(hdlg);
                    recalc_next_free_id(hdlg);
                    if (hd_listview_items > 0) {
                        settings_listview_select(hdlg, IDC_LIST_HARD_DISKS, 0);
                        lv1_current_sel = 0;
                        settings_set_cur_sel(hdlg, IDC_COMBO_HD_BUS, temp_hdd[0].bus - 1);
                    } else
                        lv1_current_sel = -1;
                    recalc_location_controls(hdlg, 0, 0);
                    ignore_change = 0;
                    return FALSE;
            }

        case WM_DPICHANGED_AFTERPARENT:
            win_settings_hard_disks_resize_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_HARD_DISKS, (const uint8_t *) hd_icons);
            break;
        default:
            return FALSE;
    }

    return FALSE;
}

static int
combo_id_to_string_id(int combo_id)
{
    return IDS_5376 + combo_id;
}

static int
combo_id_to_format_string_id(int combo_id)
{
    return IDS_5632 + combo_id;
}

static BOOL
win_settings_floppy_drives_recalc_list(HWND hdlg)
{
    LVITEM      lvI;
    char        s[256];
    const char *t;
    WCHAR       szText[256];
    HWND        hwndList = GetDlgItem(hdlg, IDC_LIST_FLOPPY_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.state = 0;

    for (uint8_t i = 0; i < FDD_NUM; i++) {
        lvI.iSubItem = 0;
        if (temp_fdd_types[i] > 0) {
            t = fdd_getname(temp_fdd_types[i]);
            strncpy(s, t, sizeof(s) - 1);
            mbstowcs(szText, s, strlen(s) + 1);
            lvI.pszText = szText;
        } else
            lvI.pszText = plat_get_string(IDS_5376);
        lvI.iItem  = i;
        lvI.iImage = temp_fdd_types[i];

        if (ListView_InsertItem(hwndList, &lvI) == -1)
            return FALSE;

        lvI.iSubItem = 1;
        lvI.pszText  = plat_get_string(temp_fdd_turbo[i] ? IDS_2060 : IDS_2061);
        lvI.iItem    = i;
        lvI.iImage   = 0;

        if (ListView_SetItem(hwndList, &lvI) == -1)
            return FALSE;

        lvI.iSubItem = 2;
        lvI.pszText  = plat_get_string(temp_fdd_check_bpb[i] ? IDS_2060 : IDS_2061);
        lvI.iItem    = i;
        lvI.iImage   = 0;

        if (ListView_SetItem(hwndList, &lvI) == -1)
            return FALSE;
    }

    return TRUE;
}

static BOOL
win_settings_cdrom_drives_recalc_list(HWND hdlg)
{
    LVITEM lvI;
    int    fsid = 0;
    WCHAR  szText[256];
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_CDROM_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    for (uint8_t i = 0; i < CDROM_NUM; i++) {
        fsid = combo_id_to_format_string_id(temp_cdrom[i].bus_type);

        lvI.iSubItem = 0;
        switch (temp_cdrom[i].bus_type) {
            default:
            case CDROM_BUS_DISABLED:
                lvI.pszText = plat_get_string(fsid);
                lvI.iImage  = 0;
                break;
            case CDROM_BUS_ATAPI:
                wsprintf(szText, plat_get_string(fsid), temp_cdrom[i].ide_channel >> 1, temp_cdrom[i].ide_channel & 1);
                lvI.pszText = szText;
                lvI.iImage  = 1;
                break;
            case CDROM_BUS_SCSI:
                wsprintf(szText, plat_get_string(fsid), temp_cdrom[i].scsi_device_id >> 4, temp_cdrom[i].scsi_device_id & 15);
                lvI.pszText = szText;
                lvI.iImage  = 1;
                break;
        }

        lvI.iItem = i;

        if (ListView_InsertItem(hwndList, &lvI) == -1)
            return FALSE;

        lvI.iSubItem = 1;
        if (temp_cdrom[i].bus_type == CDROM_BUS_DISABLED)
            lvI.pszText = plat_get_string(IDS_2104);
        else {
            wsprintf(szText, L"%ix", temp_cdrom[i].speed);
            lvI.pszText = szText;
        }
        lvI.iItem  = i;
        lvI.iImage = 0;

        if (ListView_SetItem(hwndList, &lvI) == -1)
            return FALSE;

#if 0
        lvI.iSubItem = 2;
        lvI.pszText  = plat_get_string(temp_cdrom[i].early ? IDS_2060 : IDS_2061);
        lvI.iItem    = i;
        lvI.iImage   = 0;

        if (ListView_SetItem(hwndList, &lvI) == -1)
            return FALSE;
#endif
    }

    return TRUE;
}

static BOOL
win_settings_mo_drives_recalc_list(HWND hdlg)
{
    LVITEM lvI;
    int    fsid = 0;
    WCHAR  szText[256];
    char   szType[30];
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_MO_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    for (uint8_t i = 0; i < MO_NUM; i++) {
        fsid = combo_id_to_format_string_id(temp_mo_drives[i].bus_type);

        lvI.iSubItem = 0;
        switch (temp_mo_drives[i].bus_type) {
            default:
            case MO_BUS_DISABLED:
                lvI.pszText = plat_get_string(fsid);
                lvI.iImage  = 0;
                break;
            case MO_BUS_ATAPI:
                wsprintf(szText, plat_get_string(fsid), temp_mo_drives[i].ide_channel >> 1, temp_mo_drives[i].ide_channel & 1);
                lvI.pszText = szText;
                lvI.iImage  = 1;
                break;
            case MO_BUS_SCSI:
                wsprintf(szText, plat_get_string(fsid), temp_mo_drives[i].scsi_device_id >> 4, temp_mo_drives[i].scsi_device_id & 15);
                lvI.pszText = szText;
                lvI.iImage  = 1;
                break;
        }

        lvI.iItem = i;

        if (ListView_InsertItem(hwndList, &lvI) == -1)
            return FALSE;

        lvI.iSubItem = 1;
        if (temp_mo_drives[i].bus_type == MO_BUS_DISABLED)
            lvI.pszText = plat_get_string(IDS_2104);
        else {
            memset(szType, 0, 30);
            memcpy(szType, mo_drive_types[temp_mo_drives[i].type].vendor, 8);
            szType[strlen(szType)] = ' ';
            memcpy(szType + strlen(szType), mo_drive_types[temp_mo_drives[i].type].model, 16);
            szType[strlen(szType)] = ' ';
            memcpy(szType + strlen(szType), mo_drive_types[temp_mo_drives[i].type].revision, 4);

            mbstowcs(szText, szType, strlen(szType) + 1);
            lvI.pszText = szText;
        }
        lvI.iItem  = i;
        lvI.iImage = 0;

        if (ListView_SetItem(hwndList, &lvI) == -1)
            return FALSE;
    }

    return TRUE;
}

static BOOL
win_settings_zip_drives_recalc_list(HWND hdlg)
{
    LVITEM lvI;
    int    fsid = 0;
    WCHAR  szText[256];
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_ZIP_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    for (uint8_t i = 0; i < ZIP_NUM; i++) {
        fsid = combo_id_to_format_string_id(temp_zip_drives[i].bus_type);

        lvI.iSubItem = 0;
        switch (temp_zip_drives[i].bus_type) {
            default:
            case ZIP_BUS_DISABLED:
                lvI.pszText = plat_get_string(fsid);
                lvI.iImage  = 0;
                break;
            case ZIP_BUS_ATAPI:
                wsprintf(szText, plat_get_string(fsid), temp_zip_drives[i].ide_channel >> 1, temp_zip_drives[i].ide_channel & 1);
                lvI.pszText = szText;
                lvI.iImage  = 1;
                break;
            case ZIP_BUS_SCSI:
                wsprintf(szText, plat_get_string(fsid), temp_zip_drives[i].scsi_device_id >> 4, temp_zip_drives[i].scsi_device_id & 15);
                lvI.pszText = szText;
                lvI.iImage  = 1;
                break;
        }

        lvI.iItem = i;

        if (ListView_InsertItem(hwndList, &lvI) == -1)
            return FALSE;

        lvI.iSubItem = 1;
        lvI.pszText  = plat_get_string(temp_zip_drives[i].is_250 ? IDS_5901 : IDS_5900);
        lvI.iItem    = i;
        lvI.iImage   = 0;

        if (ListView_SetItem(hwndList, &lvI) == -1)
            return FALSE;
    }

    return TRUE;
}

#define C_COLUMNS_FLOPPY_DRIVES_TYPE  292
#define C_COLUMNS_FLOPPY_DRIVES_TURBO 58
#define C_COLUMNS_FLOPPY_DRIVES_BPB   89

static void
win_settings_floppy_drives_resize_columns(HWND hdlg)
{
    int  width[C_COLUMNS_FLOPPY_DRIVES] = {
                                            C_COLUMNS_FLOPPY_DRIVES_TYPE,
                                            C_COLUMNS_FLOPPY_DRIVES_TURBO,
                                            C_COLUMNS_FLOPPY_DRIVES_BPB
                                          };
    int  total    = 0;
    HWND hwndList = GetDlgItem(hdlg, IDC_LIST_FLOPPY_DRIVES);
    RECT r;

    GetWindowRect(hwndList, &r);
    for (uint8_t iCol = 0; iCol < C_COLUMNS_FLOPPY_DRIVES; iCol++) {
        width[iCol] = MulDiv(width[iCol], dpi, 96);
        total += width[iCol];
        ListView_SetColumnWidth(hwndList, iCol, MulDiv(width[iCol], dpi, 96));
    }
    width[C_COLUMNS_FLOPPY_DRIVES - 1] = (r.right - r.left) - 4 - total;
    ListView_SetColumnWidth(hwndList, 2, width[C_COLUMNS_FLOPPY_DRIVES - 1]);
}

static BOOL
win_settings_floppy_drives_init_columns(HWND hdlg)
{
    LVCOLUMN lvc;
    HWND     hwndList = GetDlgItem(hdlg, IDC_LIST_FLOPPY_DRIVES);

    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

    /* Type */
    lvc.iSubItem = 0;
    lvc.pszText  = plat_get_string(IDS_TYPE);

    lvc.cx  = C_COLUMNS_FLOPPY_DRIVES_TYPE;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 0, &lvc) == -1)
        return FALSE;

    /* Turbo */
    lvc.iSubItem = 1;
    lvc.pszText  = plat_get_string(IDS_2059);

    lvc.cx  = C_COLUMNS_FLOPPY_DRIVES_TURBO;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 1, &lvc) == -1)
        return FALSE;

    /* Check BPB */
    lvc.iSubItem = 2;
    lvc.pszText  = plat_get_string(IDS_BPB);

    lvc.cx  = C_COLUMNS_FLOPPY_DRIVES_BPB;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 2, &lvc) == -1)
        return FALSE;

    win_settings_floppy_drives_resize_columns(hdlg);
    return TRUE;
}

#define C_COLUMNS_CDROM_DRIVES_BUS     292
#define C_COLUMNS_CDROM_DRIVES_SPEED   58
#define C_COLUMNS_CDROM_DRIVES_EARLIER 89

static void
win_settings_cdrom_drives_resize_columns(HWND hdlg)
{
    int width[C_COLUMNS_CDROM_DRIVES] = {
                                          C_COLUMNS_CDROM_DRIVES_BUS,
                                          C_COLUMNS_CDROM_DRIVES_SPEED,
                                          C_COLUMNS_CDROM_DRIVES_EARLIER
                                        };
    int  total    = 0;
    HWND hwndList = GetDlgItem(hdlg, IDC_LIST_CDROM_DRIVES);
    RECT r;

    GetWindowRect(hwndList, &r);
    for (uint8_t iCol = 0; iCol < C_COLUMNS_CDROM_DRIVES; iCol++) {
        width[iCol] = MulDiv(width[iCol], dpi, 96);
        total += width[iCol];
        ListView_SetColumnWidth(hwndList, iCol, MulDiv(width[iCol], dpi, 96));
    }
    width[C_COLUMNS_CDROM_DRIVES - 1] = (r.right - r.left) - 4 - total;
    ListView_SetColumnWidth(hwndList, 2, width[C_COLUMNS_CDROM_DRIVES - 1]);
}

static BOOL
win_settings_cdrom_drives_init_columns(HWND hdlg)
{
    LVCOLUMN lvc;
    HWND     hwndList = GetDlgItem(hdlg, IDC_LIST_CDROM_DRIVES);

    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

    /* Bus */
    lvc.iSubItem = 0;
    lvc.pszText  = plat_get_string(IDS_BUS);

    lvc.cx  = C_COLUMNS_CDROM_DRIVES_BUS;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 0, &lvc) == -1)
        return FALSE;

    /* Speed */
    lvc.iSubItem = 1;
    lvc.pszText  = plat_get_string(IDS_2053);

    lvc.cx  = C_COLUMNS_CDROM_DRIVES_SPEED;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 1, &lvc) == -1)
        return FALSE;

    /* Type */
    lvc.iSubItem = 2;
    lvc.pszText  = plat_get_string(IDS_2162);

    lvc.cx  = C_COLUMNS_CDROM_DRIVES_EARLIER;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 2, &lvc) == -1)
        return FALSE;

    win_settings_cdrom_drives_resize_columns(hdlg);
    return TRUE;
}

#define C_COLUMNS_MO_DRIVES_BUS  292
#define C_COLUMNS_MO_DRIVES_TYPE 147

static void
win_settings_mo_drives_resize_columns(HWND hdlg)
{
    int  width[C_COLUMNS_MO_DRIVES] = {
                                        C_COLUMNS_MO_DRIVES_BUS,
                                        C_COLUMNS_MO_DRIVES_TYPE
                                      };
    HWND hwndList                   = GetDlgItem(hdlg, IDC_LIST_MO_DRIVES);
    RECT r;

    GetWindowRect(hwndList, &r);
    width[0] = MulDiv(width[0], dpi, 96);
    ListView_SetColumnWidth(hwndList, 0, MulDiv(width[0], dpi, 96));
    width[C_COLUMNS_MO_DRIVES - 1] = (r.right - r.left) - 4 - width[0];
    ListView_SetColumnWidth(hwndList, 1, width[C_COLUMNS_MO_DRIVES - 1]);
}

static BOOL
win_settings_mo_drives_init_columns(HWND hdlg)
{
    LVCOLUMN lvc;
    HWND     hwndList = GetDlgItem(hdlg, IDC_LIST_MO_DRIVES);

    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

    /* Bus */
    lvc.iSubItem = 0;
    lvc.pszText  = plat_get_string(IDS_BUS);

    lvc.cx  = C_COLUMNS_MO_DRIVES_BUS;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 0, &lvc) == -1)
        return FALSE;

    /* Type */
    lvc.iSubItem = 1;
    lvc.pszText  = plat_get_string(IDS_TYPE);

    lvc.cx  = C_COLUMNS_MO_DRIVES_TYPE;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 1, &lvc) == -1)
        return FALSE;

    win_settings_mo_drives_resize_columns(hdlg);
    return TRUE;
}

#define C_COLUMNS_ZIP_DRIVES_BUS  292
#define C_COLUMNS_ZIP_DRIVES_TYPE 147

static void
win_settings_zip_drives_resize_columns(HWND hdlg)
{
    int  width[C_COLUMNS_ZIP_DRIVES] = {
                                        C_COLUMNS_ZIP_DRIVES_BUS,
                                        C_COLUMNS_ZIP_DRIVES_TYPE
                                      };
    HWND hwndList                   = GetDlgItem(hdlg, IDC_LIST_ZIP_DRIVES);
    RECT r;

    GetWindowRect(hwndList, &r);
    width[0] = MulDiv(width[0], dpi, 96);
    ListView_SetColumnWidth(hwndList, 0, MulDiv(width[0], dpi, 96));
    width[C_COLUMNS_ZIP_DRIVES - 1] = (r.right - r.left) - 4 - width[0];
    ListView_SetColumnWidth(hwndList, 1, width[C_COLUMNS_ZIP_DRIVES - 1]);
}

static BOOL
win_settings_zip_drives_init_columns(HWND hdlg)
{
    LVCOLUMN lvc;
    HWND     hwndList = GetDlgItem(hdlg, IDC_LIST_ZIP_DRIVES);

    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

    /* Bus */
    lvc.iSubItem = 0;
    lvc.pszText  = plat_get_string(IDS_BUS);

    lvc.cx  = C_COLUMNS_ZIP_DRIVES_BUS;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 0, &lvc) == -1)
        return FALSE;

    /* Type */
    lvc.iSubItem = 1;
    lvc.pszText  = plat_get_string(IDS_TYPE);

    lvc.cx  = C_COLUMNS_ZIP_DRIVES_TYPE;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, 1, &lvc) == -1)
        return FALSE;

    win_settings_zip_drives_resize_columns(hdlg);
    return TRUE;
}

static int
get_selected_drive(HWND hdlg, int id, int max)
{
    int  drive = -1;
    int  j = 0;
    HWND h;

    for (int i = 0; i < max; i++) {
        h = GetDlgItem(hdlg, id);
        j = ListView_GetItemState(h, i, LVIS_SELECTED);
        if (j)
            drive = i;
    }

    return drive;
}

static void
win_settings_floppy_drives_update_item(HWND hdlg, int i)
{
    LVITEM      lvI;
    char        s[256];
    const char *t;
    WCHAR       szText[256];
    HWND        hwndList = GetDlgItem(hdlg, IDC_LIST_FLOPPY_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    lvI.iSubItem = 0;
    lvI.iItem    = i;

    if (temp_fdd_types[i] > 0) {
        t = fdd_getname(temp_fdd_types[i]);
        strncpy(s, t, sizeof(s) - 1);
        mbstowcs(szText, s, strlen(s) + 1);
        lvI.pszText = szText;
    } else
        lvI.pszText = plat_get_string(IDS_5376);
    lvI.iImage = temp_fdd_types[i];

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;

    lvI.iSubItem = 1;
    lvI.pszText  = plat_get_string(temp_fdd_turbo[i] ? IDS_2060 : IDS_2061);
    lvI.iItem    = i;
    lvI.iImage   = 0;

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;

    lvI.iSubItem = 2;
    lvI.pszText  = plat_get_string(temp_fdd_check_bpb[i] ? IDS_2060 : IDS_2061);
    lvI.iItem    = i;
    lvI.iImage   = 0;

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;
}

static void
win_settings_cdrom_drives_update_item(HWND hdlg, int i)
{
    LVITEM lvI;
    WCHAR  szText[256];
    int    fsid;
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_CDROM_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    lvI.iSubItem = 0;
    lvI.iItem    = i;

    fsid = combo_id_to_format_string_id(temp_cdrom[i].bus_type);

    switch (temp_cdrom[i].bus_type) {
        default:
        case CDROM_BUS_DISABLED:
            lvI.pszText = plat_get_string(fsid);
            lvI.iImage  = 0;
            break;
        case CDROM_BUS_ATAPI:
            wsprintf(szText, plat_get_string(fsid), temp_cdrom[i].ide_channel >> 1, temp_cdrom[i].ide_channel & 1);
            lvI.pszText = szText;
            lvI.iImage  = 1;
            break;
        case CDROM_BUS_SCSI:
            wsprintf(szText, plat_get_string(fsid), temp_cdrom[i].scsi_device_id >> 4, temp_cdrom[i].scsi_device_id & 15);
            lvI.pszText = szText;
            lvI.iImage  = 1;
            break;
    }

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;

    lvI.iSubItem = 1;
    if (temp_cdrom[i].bus_type == CDROM_BUS_DISABLED)
        lvI.pszText = plat_get_string(IDS_2104);
    else {
        wsprintf(szText, L"%ix", temp_cdrom[i].speed);
        lvI.pszText = szText;
    }
    lvI.iItem  = i;
    lvI.iImage = 0;

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;

#if 0
    lvI.iSubItem = 2;
    lvI.pszText  = plat_get_string(temp_cdrom[i].early ? IDS_2060 : IDS_2061);
    lvI.iItem    = i;
    lvI.iImage   = 0;

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;
#endif
}

static void
win_settings_mo_drives_update_item(HWND hdlg, int i)
{
    LVITEM lvI;
    WCHAR  szText[256];
    char   szType[30];
    int    fsid;
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_MO_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    /* Bus */
    lvI.iSubItem = 0;
    lvI.iItem    = i;

    fsid = combo_id_to_format_string_id(temp_mo_drives[i].bus_type);

    switch (temp_mo_drives[i].bus_type) {
        default:
        case MO_BUS_DISABLED:
            lvI.pszText = plat_get_string(fsid);
            lvI.iImage  = 0;
            break;
        case MO_BUS_ATAPI:
            wsprintf(szText, plat_get_string(fsid), temp_mo_drives[i].ide_channel >> 1, temp_mo_drives[i].ide_channel & 1);
            lvI.pszText = szText;
            lvI.iImage  = 1;
            break;
        case MO_BUS_SCSI:
            wsprintf(szText, plat_get_string(fsid), temp_mo_drives[i].scsi_device_id >> 4, temp_mo_drives[i].scsi_device_id & 15);
            lvI.pszText = szText;
            lvI.iImage  = 1;
            break;
    }

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;

    /* Type */
    lvI.iSubItem = 1;
    if (temp_mo_drives[i].bus_type == MO_BUS_DISABLED)
        lvI.pszText = plat_get_string(IDS_2104);
    else {
        memset(szType, 0, 30);
        memcpy(szType, mo_drive_types[temp_mo_drives[i].type].vendor, 8);
        szType[strlen(szType)] = ' ';
        memcpy(szType + strlen(szType), mo_drive_types[temp_mo_drives[i].type].model, 16);
        szType[strlen(szType)] = ' ';
        memcpy(szType + strlen(szType), mo_drive_types[temp_mo_drives[i].type].revision, 4);
        mbstowcs(szText, szType, strlen(szType) + 1);
        lvI.pszText = szText;
    }
    lvI.iItem  = i;
    lvI.iImage = 0;

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;
}

static void
win_settings_zip_drives_update_item(HWND hdlg, int i)
{
    LVITEM lvI;
    WCHAR  szText[256];
    int    fsid;
    HWND   hwndList = GetDlgItem(hdlg, IDC_LIST_ZIP_DRIVES);

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    lvI.iSubItem = 0;
    lvI.iItem    = i;

    fsid = combo_id_to_format_string_id(temp_zip_drives[i].bus_type);

    switch (temp_zip_drives[i].bus_type) {
        default:
        case ZIP_BUS_DISABLED:
            lvI.pszText = plat_get_string(fsid);
            lvI.iImage  = 0;
            break;
        case ZIP_BUS_ATAPI:
            wsprintf(szText, plat_get_string(fsid), temp_zip_drives[i].ide_channel >> 1, temp_zip_drives[i].ide_channel & 1);
            lvI.pszText = szText;
            lvI.iImage  = 1;
            break;
        case ZIP_BUS_SCSI:
            wsprintf(szText, plat_get_string(fsid), temp_zip_drives[i].scsi_device_id >> 4, temp_zip_drives[i].scsi_device_id & 15);
            lvI.pszText = szText;
            lvI.iImage  = 1;
            break;
    }

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;

    lvI.iSubItem = 1;
    lvI.pszText  = plat_get_string(temp_zip_drives[i].is_250 ? IDS_5901 : IDS_5900);
    lvI.iItem    = i;
    lvI.iImage   = 0;

    if (ListView_SetItem(hwndList, &lvI) == -1)
        return;
}

static void
cdrom_add_locations(HWND hdlg)
{
    LPTSTR lptsTemp;
    int    i = 0;

    lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

    for (i = CDROM_BUS_DISABLED; i <= CDROM_BUS_SCSI; i++) {
        if ((i == CDROM_BUS_DISABLED) || (i >= CDROM_BUS_ATAPI))
            settings_add_string(hdlg, IDC_COMBO_CD_BUS, win_get_string(combo_id_to_string_id(i)));
    }

    for (i = 1; i <= 72; i++) {
        wsprintf(lptsTemp, L"%ix", i);
        settings_add_string(hdlg, IDC_COMBO_CD_SPEED, (LPARAM) lptsTemp);
    }

    for (i = 0; i < (SCSI_BUS_MAX * SCSI_ID_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4135), i >> 4, i & 15);
        settings_add_string(hdlg, IDC_COMBO_CD_ID, (LPARAM) lptsTemp);
    }

    for (i = 0; i < (IDE_BUS_MAX * IDE_CHAN_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4097), i >> 1, i & 1);
        settings_add_string(hdlg, IDC_COMBO_CD_CHANNEL_IDE, (LPARAM) lptsTemp);
    }

    free(lptsTemp);
}

static void
cdrom_recalc_location_controls(HWND hdlg, int assign_id)
{
    int bus = temp_cdrom[lv2_current_sel].bus_type;

    for (uint16_t i = IDT_CD_ID; i <= IDT_CD_CHANNEL; i++)
        settings_show_window(hdlg, i, FALSE);
    settings_show_window(hdlg, IDC_COMBO_CD_ID, FALSE);
    settings_show_window(hdlg, IDC_COMBO_CD_CHANNEL_IDE, FALSE);
    settings_show_window(hdlg, IDC_COMBO_CD_SPEED, bus != CDROM_BUS_DISABLED);
    settings_show_window(hdlg, IDT_CD_SPEED, bus != CDROM_BUS_DISABLED);
#if 0
    settings_show_window(hdlg, IDC_COMBO_CD_TYPE, bus != CDROM_BUS_DISABLED);
#endif
    if (bus != CDROM_BUS_DISABLED) {
        settings_set_cur_sel(hdlg, IDC_COMBO_CD_SPEED, temp_cdrom[lv2_current_sel].speed - 1);
#if 0
    settings_set_check(hdlg, IDC_COMBO_CD_TYPE, temp_cdrom[lv2_current_sel].early);
#endif
    }

    switch (bus) {
        case CDROM_BUS_ATAPI: /* ATAPI */
            settings_show_window(hdlg, IDT_CD_CHANNEL, TRUE);
            settings_show_window(hdlg, IDC_COMBO_CD_CHANNEL_IDE, TRUE);

            if (assign_id)
                temp_cdrom[lv2_current_sel].ide_channel = next_free_ide_channel();

            settings_set_cur_sel(hdlg, IDC_COMBO_CD_CHANNEL_IDE, temp_cdrom[lv2_current_sel].ide_channel);
            break;
        case CDROM_BUS_SCSI: /* SCSI */
            settings_show_window(hdlg, IDT_CD_ID, TRUE);
            settings_show_window(hdlg, IDC_COMBO_CD_ID, TRUE);

            if (assign_id)
                next_free_scsi_id(&temp_cdrom[lv2_current_sel].scsi_device_id);

            settings_set_cur_sel(hdlg, IDC_COMBO_CD_ID, temp_cdrom[lv2_current_sel].scsi_device_id);
            break;
    }
}

static void
mo_add_locations(HWND hdlg)
{
    LPTSTR lptsTemp;
    char  *temp;
    int    i = 0;

    lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));
    temp     = (char *) malloc(30 * sizeof(char));

    for (i = MO_BUS_DISABLED; i <= MO_BUS_SCSI; i++) {
        if ((i == MO_BUS_DISABLED) || (i >= MO_BUS_ATAPI))
            settings_add_string(hdlg, IDC_COMBO_MO_BUS, win_get_string(combo_id_to_string_id(i)));
    }

    for (i = 0; i < (SCSI_BUS_MAX * SCSI_ID_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4135), i >> 4, i & 15);
        settings_add_string(hdlg, IDC_COMBO_MO_ID, (LPARAM) lptsTemp);
    }

    for (i = 0; i < (IDE_BUS_MAX * IDE_CHAN_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4097), i >> 1, i & 1);
        settings_add_string(hdlg, IDC_COMBO_MO_CHANNEL_IDE, (LPARAM) lptsTemp);
    }

    for (int i = 0; i < KNOWN_MO_DRIVE_TYPES; i++) {
        memset(temp, 0, 30);
        memcpy(temp, mo_drive_types[i].vendor, 8);
        temp[strlen(temp)] = ' ';
        memcpy(temp + strlen(temp), mo_drive_types[i].model, 16);
        temp[strlen(temp)] = ' ';
        memcpy(temp + strlen(temp), mo_drive_types[i].revision, 4);

        mbstowcs(lptsTemp, temp, strlen(temp) + 1);
        settings_add_string(hdlg, IDC_COMBO_MO_TYPE, (LPARAM) lptsTemp);
    }

    free(temp);
    free(lptsTemp);
}

static void
mo_recalc_location_controls(HWND hdlg, int assign_id)
{
    int bus = temp_mo_drives[lv1_current_sel].bus_type;

    for (int i = IDT_MO_ID; i <= IDT_MO_CHANNEL; i++)
        settings_show_window(hdlg, i, FALSE);
    settings_show_window(hdlg, IDC_COMBO_MO_ID, FALSE);
    settings_show_window(hdlg, IDC_COMBO_MO_CHANNEL_IDE, FALSE);
    settings_show_window(hdlg, IDC_COMBO_MO_TYPE, bus != MO_BUS_DISABLED);
    settings_show_window(hdlg, IDT_MO_TYPE, bus != MO_BUS_DISABLED);

    if (bus != MO_BUS_DISABLED)
        settings_set_cur_sel(hdlg, IDC_COMBO_MO_TYPE, temp_mo_drives[lv1_current_sel].type);

    switch (bus) {
        case MO_BUS_ATAPI: /* ATAPI */
            settings_show_window(hdlg, IDT_MO_CHANNEL, TRUE);
            settings_show_window(hdlg, IDC_COMBO_MO_CHANNEL_IDE, TRUE);

            if (assign_id)
                temp_mo_drives[lv1_current_sel].ide_channel = next_free_ide_channel();

            settings_set_cur_sel(hdlg, IDC_COMBO_MO_CHANNEL_IDE, temp_mo_drives[lv1_current_sel].ide_channel);
            break;
        case MO_BUS_SCSI: /* SCSI */
            settings_show_window(hdlg, IDT_MO_ID, TRUE);
            settings_show_window(hdlg, IDC_COMBO_MO_ID, TRUE);

            if (assign_id)
                next_free_scsi_id(&temp_mo_drives[lv1_current_sel].scsi_device_id);

            settings_set_cur_sel(hdlg, IDC_COMBO_MO_ID, temp_mo_drives[lv1_current_sel].scsi_device_id);
            break;
    }
}

static void
zip_add_locations(HWND hdlg)
{
    LPTSTR lptsTemp;
    int    i = 0;

    lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));

    for (i = ZIP_BUS_DISABLED; i <= ZIP_BUS_SCSI; i++) {
        if ((i == ZIP_BUS_DISABLED) || (i >= ZIP_BUS_ATAPI))
            settings_add_string(hdlg, IDC_COMBO_ZIP_BUS, win_get_string(combo_id_to_string_id(i)));
    }

    for (i = 0; i < (SCSI_BUS_MAX * SCSI_LUN_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4135), i >> 4, i & 15);
        settings_add_string(hdlg, IDC_COMBO_ZIP_ID, (LPARAM) lptsTemp);
    }

    for (i = 0; i < (IDE_BUS_MAX * IDE_CHAN_MAX); i++) {
        wsprintf(lptsTemp, plat_get_string(IDS_4097), i >> 1, i & 1);
        settings_add_string(hdlg, IDC_COMBO_ZIP_CHANNEL_IDE, (LPARAM) lptsTemp);
    }

    free(lptsTemp);
}

static void
zip_recalc_location_controls(HWND hdlg, int assign_id)
{
    int bus = temp_zip_drives[lv2_current_sel].bus_type;

    for (int i = IDT_ZIP_ID; i <= IDT_ZIP_LUN; i++)
        settings_show_window(hdlg, i, FALSE);
    settings_show_window(hdlg, IDC_COMBO_ZIP_ID, FALSE);
    settings_show_window(hdlg, IDC_COMBO_ZIP_CHANNEL_IDE, FALSE);
    settings_show_window(hdlg, IDC_CHECK250, bus != ZIP_BUS_DISABLED);

    if (bus != ZIP_BUS_DISABLED)
        settings_set_check(hdlg, IDC_CHECK250, temp_zip_drives[lv2_current_sel].is_250);

    switch (bus) {
        case ZIP_BUS_ATAPI: /* ATAPI */
            settings_show_window(hdlg, IDT_ZIP_LUN, TRUE);
            settings_show_window(hdlg, IDC_COMBO_ZIP_CHANNEL_IDE, TRUE);

            if (assign_id)
                temp_zip_drives[lv2_current_sel].ide_channel = next_free_ide_channel();

            settings_set_cur_sel(hdlg, IDC_COMBO_ZIP_CHANNEL_IDE, temp_zip_drives[lv2_current_sel].ide_channel);
            break;
        case ZIP_BUS_SCSI: /* SCSI */
            settings_show_window(hdlg, IDT_ZIP_ID, TRUE);
            settings_show_window(hdlg, IDC_COMBO_ZIP_ID, TRUE);

            if (assign_id)
                next_free_scsi_id(&temp_zip_drives[lv2_current_sel].scsi_device_id);

            settings_set_cur_sel(hdlg, IDC_COMBO_ZIP_ID, temp_zip_drives[lv2_current_sel].scsi_device_id);
            break;
    }
}

static void
cdrom_track(uint8_t id)
{
    if (temp_cdrom[id].bus_type == CDROM_BUS_ATAPI)
        ide_tracking |= (2 << (temp_cdrom[id].ide_channel << 3));
    else if (temp_cdrom[id].bus_type == CDROM_BUS_SCSI)
        scsi_tracking[temp_cdrom[id].scsi_device_id >> 3] |= (1 << (temp_cdrom[id].scsi_device_id & 0x07));
}

static void
cdrom_untrack(uint8_t id)
{
    if (temp_cdrom[id].bus_type == CDROM_BUS_ATAPI)
        ide_tracking &= ~(2 << (temp_cdrom[id].ide_channel << 3));
    else if (temp_cdrom[id].bus_type == CDROM_BUS_SCSI)
        scsi_tracking[temp_cdrom[id].scsi_device_id >> 3] &= ~(1 << (temp_cdrom[id].scsi_device_id & 0x07));
}

static void
zip_track(uint8_t id)
{
    if (temp_zip_drives[id].bus_type == ZIP_BUS_ATAPI)
        ide_tracking |= (1 << temp_zip_drives[id].ide_channel);
    else if (temp_zip_drives[id].bus_type == ZIP_BUS_SCSI)
        scsi_tracking[temp_zip_drives[id].scsi_device_id >> 3] |= (1 << (temp_zip_drives[id].scsi_device_id & 0x07));
}

static void
zip_untrack(uint8_t id)
{
    if (temp_zip_drives[id].bus_type == ZIP_BUS_ATAPI)
        ide_tracking &= ~(1 << temp_zip_drives[id].ide_channel);
    else if (temp_zip_drives[id].bus_type == ZIP_BUS_SCSI)
        scsi_tracking[temp_zip_drives[id].scsi_device_id >> 3] &= ~(1 << (temp_zip_drives[id].scsi_device_id & 0x07));
}

static void
mo_track(uint8_t id)
{
    if (temp_mo_drives[id].bus_type == MO_BUS_ATAPI)
        ide_tracking |= (1 << (temp_mo_drives[id].ide_channel << 3));
    else if (temp_mo_drives[id].bus_type == MO_BUS_SCSI)
        scsi_tracking[temp_mo_drives[id].scsi_device_id >> 3] |= (1 << (temp_mo_drives[id].scsi_device_id & 0x07));
}

static void
mo_untrack(uint8_t id)
{
    if (temp_mo_drives[id].bus_type == MO_BUS_ATAPI)
        ide_tracking &= ~(1 << (temp_mo_drives[id].ide_channel << 3));
    else if (temp_mo_drives[id].bus_type == MO_BUS_SCSI)
        scsi_tracking[temp_mo_drives[id].scsi_device_id >> 3] &= ~(1 << (temp_mo_drives[id].scsi_device_id & 0x07));
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_floppy_and_cdrom_drives_proc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    int           old_sel = 0;
    int           b = 0;
    int           assign = 0;
    uint32_t      b2 = 0;
    WCHAR         szText[256];
    const uint8_t fd_icons[15] = { 248, 16, 16, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 0 };
    const uint8_t cd_icons[3]  = { 249, 32, 0 };

    switch (message) {
        case WM_INITDIALOG:
            ignore_change = 1;

            lv1_current_sel = 0;
            win_settings_floppy_drives_init_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_FLOPPY_DRIVES, (const uint8_t *) fd_icons);
            win_settings_floppy_drives_recalc_list(hdlg);
            settings_listview_select(hdlg, IDC_LIST_FLOPPY_DRIVES, 0);
            for (uint8_t i = 0; i < 14; i++) {
                if (i == 0)
                    settings_add_string(hdlg, IDC_COMBO_FD_TYPE, win_get_string(IDS_5376));
                else {
                    mbstowcs(szText, fdd_getname(i), strlen(fdd_getname(i)) + 1);
                    settings_add_string(hdlg, IDC_COMBO_FD_TYPE, (LPARAM) szText);
                }
            }
            settings_set_cur_sel(hdlg, IDC_COMBO_FD_TYPE, temp_fdd_types[lv1_current_sel]);

            settings_set_check(hdlg, IDC_CHECKTURBO, temp_fdd_turbo[lv1_current_sel]);
            settings_set_check(hdlg, IDC_CHECKBPB, temp_fdd_check_bpb[lv1_current_sel]);

            settings_listview_enable_styles(hdlg, IDC_LIST_FLOPPY_DRIVES);

            lv2_current_sel = 0;
            win_settings_cdrom_drives_init_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_CDROM_DRIVES, (const uint8_t *) cd_icons);
            win_settings_cdrom_drives_recalc_list(hdlg);
            settings_listview_select(hdlg, IDC_LIST_CDROM_DRIVES, 0);
            cdrom_add_locations(hdlg);

            switch (temp_cdrom[lv2_current_sel].bus_type) {
                default:
                case CDROM_BUS_DISABLED:
                    b = 0;
                    break;
                case CDROM_BUS_ATAPI:
                    b = 1;
                    break;
                case CDROM_BUS_SCSI:
                    b = 2;
                    break;
            }
            settings_set_cur_sel(hdlg, IDC_COMBO_CD_BUS, b);
            cdrom_recalc_location_controls(hdlg, 0);

            settings_listview_enable_styles(hdlg, IDC_LIST_CDROM_DRIVES);

            ignore_change = 0;
            return TRUE;

        case WM_NOTIFY:
            if (ignore_change)
                return FALSE;

            if ((((LPNMHDR) lParam)->code == LVN_ITEMCHANGED) && (((LPNMHDR) lParam)->idFrom == IDC_LIST_FLOPPY_DRIVES)) {
                old_sel         = lv1_current_sel;
                lv1_current_sel = get_selected_drive(hdlg, IDC_LIST_FLOPPY_DRIVES, FDD_NUM);
                if (lv1_current_sel == old_sel)
                    return FALSE;
                ignore_change = 1;
                settings_set_cur_sel(hdlg, IDC_COMBO_FD_TYPE, temp_fdd_types[lv1_current_sel]);
                settings_set_check(hdlg, IDC_CHECKTURBO, temp_fdd_turbo[lv1_current_sel]);
                settings_set_check(hdlg, IDC_CHECKBPB, temp_fdd_check_bpb[lv1_current_sel]);
                ignore_change = 0;
            } else if ((((LPNMHDR) lParam)->code == LVN_ITEMCHANGED) && (((LPNMHDR) lParam)->idFrom == IDC_LIST_CDROM_DRIVES)) {
                old_sel         = lv2_current_sel;
                lv2_current_sel = get_selected_drive(hdlg, IDC_LIST_CDROM_DRIVES, CDROM_NUM);
                if (lv2_current_sel == old_sel)
                    return FALSE;
                ignore_change = 1;

                switch (temp_cdrom[lv2_current_sel].bus_type) {
                    default:
                    case CDROM_BUS_DISABLED:
                        b = 0;
                        break;
                    case CDROM_BUS_ATAPI:
                        b = 1;
                        break;
                    case CDROM_BUS_SCSI:
                        b = 2;
                        break;
                }
                settings_set_cur_sel(hdlg, IDC_COMBO_CD_BUS, b);

                cdrom_recalc_location_controls(hdlg, 0);
                ignore_change = 0;
            }
            break;

        case WM_COMMAND:
            if (ignore_change)
                return FALSE;

            ignore_change = 1;
            switch (LOWORD(wParam)) {
                case IDC_COMBO_FD_TYPE:
                    temp_fdd_types[lv1_current_sel] = settings_get_cur_sel(hdlg, IDC_COMBO_FD_TYPE);
                    win_settings_floppy_drives_update_item(hdlg, lv1_current_sel);
                    break;

                case IDC_CHECKTURBO:
                    temp_fdd_turbo[lv1_current_sel] = settings_get_check(hdlg, IDC_CHECKTURBO);
                    win_settings_floppy_drives_update_item(hdlg, lv1_current_sel);
                    break;

                case IDC_CHECKBPB:
                    temp_fdd_check_bpb[lv1_current_sel] = settings_get_check(hdlg, IDC_CHECKBPB);
                    win_settings_floppy_drives_update_item(hdlg, lv1_current_sel);
                    break;

                case IDC_COMBO_CD_BUS:
                    b = settings_get_cur_sel(hdlg, IDC_COMBO_CD_BUS);
                    switch (b) {
                        case 0:
                            b2 = CDROM_BUS_DISABLED;
                            break;
                        case 1:
                            b2 = CDROM_BUS_ATAPI;
                            break;
                        case 2:
                            b2 = CDROM_BUS_SCSI;
                            break;
                    }
                    if (b2 == temp_cdrom[lv2_current_sel].bus_type)
                        break;
                    cdrom_untrack(lv2_current_sel);
                    assign = (temp_cdrom[lv2_current_sel].bus_type == b2) ? 0 : 1;
                    if (temp_cdrom[lv2_current_sel].bus_type == CDROM_BUS_DISABLED)
                        temp_cdrom[lv2_current_sel].speed = 8;
                    temp_cdrom[lv2_current_sel].bus_type = b2;
                    cdrom_recalc_location_controls(hdlg, assign);
                    cdrom_track(lv2_current_sel);
                    win_settings_cdrom_drives_update_item(hdlg, lv2_current_sel);
                    break;

                case IDC_COMBO_CD_ID:
                    cdrom_untrack(lv2_current_sel);
                    temp_cdrom[lv2_current_sel].scsi_device_id = settings_get_cur_sel(hdlg, IDC_COMBO_CD_ID);
                    cdrom_track(lv2_current_sel);
                    win_settings_cdrom_drives_update_item(hdlg, lv2_current_sel);
                    break;

                case IDC_COMBO_CD_CHANNEL_IDE:
                    cdrom_untrack(lv2_current_sel);
                    temp_cdrom[lv2_current_sel].ide_channel = settings_get_cur_sel(hdlg, IDC_COMBO_CD_CHANNEL_IDE);
                    cdrom_track(lv2_current_sel);
                    win_settings_cdrom_drives_update_item(hdlg, lv2_current_sel);
                    break;

                case IDC_COMBO_CD_SPEED:
                    temp_cdrom[lv2_current_sel].speed = settings_get_cur_sel(hdlg, IDC_COMBO_CD_SPEED) + 1;
                    win_settings_cdrom_drives_update_item(hdlg, lv2_current_sel);
                    break;

#if 0
                case IDC_COMBO_CD_TYPE::
                    temp_cdrom[lv2_current_sel].early = settings_get_check(hdlg, IDC_COMBO_CD_TYPE:);
                    win_settings_cdrom_drives_update_item(hdlg, lv2_current_sel);
                    break;
#endif
            }
            ignore_change = 0;

        case WM_DPICHANGED_AFTERPARENT:
            win_settings_floppy_drives_resize_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_FLOPPY_DRIVES, (const uint8_t *) fd_icons);
            win_settings_cdrom_drives_resize_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_CDROM_DRIVES, (const uint8_t *) cd_icons);
            break;
        default:
            return FALSE;
    }

    return FALSE;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_other_removable_devices_proc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    int           old_sel = 0;
    int           b = 0;
    int           assign = 0;
    uint32_t      b2           = 0;
    const uint8_t mo_icons[3]  = { 251, 56, 0 };
    const uint8_t zip_icons[3] = { 250, 48, 0 };

    switch (message) {
        case WM_INITDIALOG:
            ignore_change = 1;

            lv1_current_sel = 0;
            win_settings_mo_drives_init_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_MO_DRIVES, (const uint8_t *) mo_icons);
            win_settings_mo_drives_recalc_list(hdlg);
            settings_listview_select(hdlg, IDC_LIST_MO_DRIVES, 0);
            mo_add_locations(hdlg);

            switch (temp_mo_drives[lv1_current_sel].bus_type) {
                default:
                case MO_BUS_DISABLED:
                    b = 0;
                    break;
                case MO_BUS_ATAPI:
                    b = 1;
                    break;
                case MO_BUS_SCSI:
                    b = 2;
                    break;
            }
            settings_set_cur_sel(hdlg, IDC_COMBO_MO_BUS, b);
            mo_recalc_location_controls(hdlg, 0);

            settings_listview_enable_styles(hdlg, IDC_LIST_MO_DRIVES);

            lv2_current_sel = 0;
            win_settings_zip_drives_init_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_ZIP_DRIVES, (const uint8_t *) zip_icons);
            win_settings_zip_drives_recalc_list(hdlg);
            settings_listview_select(hdlg, IDC_LIST_ZIP_DRIVES, 0);
            zip_add_locations(hdlg);

            switch (temp_zip_drives[lv2_current_sel].bus_type) {
                default:
                case ZIP_BUS_DISABLED:
                    b = 0;
                    break;
                case ZIP_BUS_ATAPI:
                    b = 1;
                    break;
                case ZIP_BUS_SCSI:
                    b = 2;
                    break;
            }
            settings_set_cur_sel(hdlg, IDC_COMBO_ZIP_BUS, b);
            zip_recalc_location_controls(hdlg, 0);

            settings_listview_enable_styles(hdlg, IDC_LIST_ZIP_DRIVES);

            ignore_change = 0;
            return TRUE;

        case WM_NOTIFY:
            if (ignore_change)
                return FALSE;

            if ((((LPNMHDR) lParam)->code == LVN_ITEMCHANGED) && (((LPNMHDR) lParam)->idFrom == IDC_LIST_MO_DRIVES)) {
                old_sel         = lv1_current_sel;
                lv1_current_sel = get_selected_drive(hdlg, IDC_LIST_MO_DRIVES, MO_NUM);
                if (lv1_current_sel == old_sel)
                    return FALSE;
                ignore_change = 1;

                switch (temp_mo_drives[lv1_current_sel].bus_type) {
                    default:
                    case MO_BUS_DISABLED:
                        b = 0;
                        break;
                    case MO_BUS_ATAPI:
                        b = 1;
                        break;
                    case MO_BUS_SCSI:
                        b = 2;
                        break;
                }
                settings_set_cur_sel(hdlg, IDC_COMBO_MO_BUS, b);

                mo_recalc_location_controls(hdlg, 0);
                ignore_change = 0;
            } else if ((((LPNMHDR) lParam)->code == LVN_ITEMCHANGED) && (((LPNMHDR) lParam)->idFrom == IDC_LIST_ZIP_DRIVES)) {
                old_sel         = lv2_current_sel;
                lv2_current_sel = get_selected_drive(hdlg, IDC_LIST_ZIP_DRIVES, ZIP_NUM);
                if (lv2_current_sel == old_sel)
                    return FALSE;
                ignore_change = 1;

                switch (temp_zip_drives[lv2_current_sel].bus_type) {
                    default:
                    case ZIP_BUS_DISABLED:
                        b = 0;
                        break;
                    case ZIP_BUS_ATAPI:
                        b = 1;
                        break;
                    case ZIP_BUS_SCSI:
                        b = 2;
                        break;
                }
                settings_set_cur_sel(hdlg, IDC_COMBO_ZIP_BUS, b);

                zip_recalc_location_controls(hdlg, 0);
                ignore_change = 0;
            }
            break;

        case WM_COMMAND:
            if (ignore_change)
                return FALSE;

            ignore_change = 1;
            switch (LOWORD(wParam)) {
                case IDC_COMBO_MO_BUS:
                    b = settings_get_cur_sel(hdlg, IDC_COMBO_MO_BUS);
                    switch (b) {
                        case 0:
                            b2 = MO_BUS_DISABLED;
                            break;
                        case 1:
                            b2 = MO_BUS_ATAPI;
                            break;
                        case 2:
                            b2 = MO_BUS_SCSI;
                            break;
                    }
                    if (b2 == temp_mo_drives[lv1_current_sel].bus_type)
                        break;
                    mo_untrack(lv1_current_sel);
                    assign = (temp_mo_drives[lv1_current_sel].bus_type == b2) ? 0 : 1;
                    if (temp_mo_drives[lv1_current_sel].bus_type == MO_BUS_DISABLED)
                        temp_mo_drives[lv1_current_sel].type = 0;
                    temp_mo_drives[lv1_current_sel].bus_type = b2;
                    mo_recalc_location_controls(hdlg, assign);
                    mo_track(lv1_current_sel);
                    win_settings_mo_drives_update_item(hdlg, lv1_current_sel);
                    break;

                case IDC_COMBO_MO_ID:
                    mo_untrack(lv1_current_sel);
                    temp_mo_drives[lv1_current_sel].scsi_device_id = settings_get_cur_sel(hdlg, IDC_COMBO_MO_ID);
                    mo_track(lv1_current_sel);
                    win_settings_mo_drives_update_item(hdlg, lv1_current_sel);
                    break;

                case IDC_COMBO_MO_CHANNEL_IDE:
                    mo_untrack(lv1_current_sel);
                    temp_mo_drives[lv1_current_sel].ide_channel = settings_get_cur_sel(hdlg, IDC_COMBO_MO_CHANNEL_IDE);
                    mo_track(lv1_current_sel);
                    win_settings_mo_drives_update_item(hdlg, lv1_current_sel);
                    break;

                case IDC_COMBO_MO_TYPE:
                    temp_mo_drives[lv1_current_sel].type = settings_get_cur_sel(hdlg, IDC_COMBO_MO_TYPE);
                    win_settings_mo_drives_update_item(hdlg, lv1_current_sel);
                    break;

                case IDC_COMBO_ZIP_BUS:
                    b = settings_get_cur_sel(hdlg, IDC_COMBO_ZIP_BUS);
                    switch (b) {
                        case 0:
                            b2 = ZIP_BUS_DISABLED;
                            break;
                        case 1:
                            b2 = ZIP_BUS_ATAPI;
                            break;
                        case 2:
                            b2 = ZIP_BUS_SCSI;
                            break;
                    }
                    if (b2 == temp_zip_drives[lv2_current_sel].bus_type)
                        break;
                    zip_untrack(lv2_current_sel);
                    assign                                    = (temp_zip_drives[lv2_current_sel].bus_type == b2) ? 0 : 1;
                    temp_zip_drives[lv2_current_sel].bus_type = b2;
                    zip_recalc_location_controls(hdlg, assign);
                    zip_track(lv2_current_sel);
                    win_settings_zip_drives_update_item(hdlg, lv2_current_sel);
                    break;

                case IDC_COMBO_ZIP_ID:
                    zip_untrack(lv2_current_sel);
                    temp_zip_drives[lv2_current_sel].scsi_device_id = settings_get_cur_sel(hdlg, IDC_COMBO_ZIP_ID);
                    zip_track(lv2_current_sel);
                    win_settings_zip_drives_update_item(hdlg, lv2_current_sel);
                    break;

                case IDC_COMBO_ZIP_CHANNEL_IDE:
                    zip_untrack(lv2_current_sel);
                    temp_zip_drives[lv2_current_sel].ide_channel = settings_get_cur_sel(hdlg, IDC_COMBO_ZIP_CHANNEL_IDE);
                    zip_track(lv2_current_sel);
                    win_settings_zip_drives_update_item(hdlg, lv2_current_sel);
                    break;

                case IDC_CHECK250:
                    temp_zip_drives[lv2_current_sel].is_250 = settings_get_check(hdlg, IDC_CHECK250);
                    win_settings_zip_drives_update_item(hdlg, lv2_current_sel);
                    break;
            }
            ignore_change = 0;

        case WM_DPICHANGED_AFTERPARENT:
            win_settings_mo_drives_resize_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_MO_DRIVES, (const uint8_t *) mo_icons);
            win_settings_zip_drives_resize_columns(hdlg);
            image_list_init(hdlg, IDC_LIST_ZIP_DRIVES, (const uint8_t *) zip_icons);
            break;
        default:
            return FALSE;
    }

    return FALSE;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_peripherals_proc(HWND hdlg, UINT message, WPARAM wParam, UNUSED(LPARAM lParam))
{
    int             c;
    int             d;
    int             e;
    LPTSTR          lptsTemp;
    char           *stransi;
    const device_t *dev;

    switch (message) {
        case WM_INITDIALOG:
            lptsTemp = (LPTSTR) malloc(512 * sizeof(WCHAR));
            stransi  = (char *) malloc(512);

            /* Populate the ISA RTC card dropdown. */
            e = 0;
            settings_reset_content(hdlg, IDC_COMBO_ISARTC);
            for (d = 0;; d++) {
                generate_device_name(isartc_get_device(d), isartc_get_internal_name(d), 0);

                if (!device_name[0])
                    break;
                dev = isartc_get_device(d);
                if (device_is_valid(dev, temp_machine)) {
                    if (d == 0) {
                        settings_add_string(hdlg, IDC_COMBO_ISARTC, win_get_string(IDS_2104));
                        settings_set_cur_sel(hdlg, IDC_COMBO_ISARTC, 0);
                    } else
                        settings_add_string(hdlg, IDC_COMBO_ISARTC, (LPARAM) device_name);
                    settings_list_to_device[1][e] = d;
                    if (d == temp_isartc)
                        settings_set_cur_sel(hdlg, IDC_COMBO_ISARTC, e);
                    e++;
                }
            }
            settings_enable_window(hdlg, IDC_COMBO_ISARTC, machine_has_bus(temp_machine, MACHINE_BUS_ISA));
            settings_enable_window(hdlg, IDC_CONFIGURE_ISARTC, ((temp_isartc != 0) && machine_has_bus(temp_machine, MACHINE_BUS_ISA)));

            /* Populate the ISA memory card dropdowns. */
            for (c = 0; c < ISAMEM_MAX; c++) {
                e = 0;
                settings_reset_content(hdlg, IDC_COMBO_ISAMEM_1 + c);
                for (d = 0;; d++) {
                    generate_device_name(isamem_get_device(d), (char *) isamem_get_internal_name(d), 0);

                    if (!device_name[0])
                        break;

                    dev = isamem_get_device(d);
                    if (device_is_valid(dev, temp_machine)) {
                        if (d == 0) {
                            settings_add_string(hdlg, IDC_COMBO_ISAMEM_1 + c, win_get_string(IDS_2104));
                            settings_set_cur_sel(hdlg, IDC_COMBO_ISAMEM_1 + c, 0);
                        } else
                            settings_add_string(hdlg, IDC_COMBO_ISAMEM_1 + c, (LPARAM) device_name);
                        settings_list_to_device[0][e] = d;
                        if (d == temp_isamem[c])
                            settings_set_cur_sel(hdlg, IDC_COMBO_ISAMEM_1 + c, e);
                        e++;
                    }
                }
                settings_enable_window(hdlg, IDC_COMBO_ISAMEM_1 + c, machine_has_bus(temp_machine, MACHINE_BUS_ISA));
                settings_enable_window(hdlg, IDC_CONFIGURE_ISAMEM_1 + c, ((temp_isamem[c] != 0) && machine_has_bus(temp_machine, MACHINE_BUS_ISA)));
            }

            settings_enable_window(hdlg, IDC_CHECK_BUGGER, machine_has_bus(temp_machine, MACHINE_BUS_ISA));
            settings_set_check(hdlg, IDC_CHECK_BUGGER, (temp_bugger && machine_has_bus(temp_machine, MACHINE_BUS_ISA)));
            settings_set_check(hdlg, IDC_CHECK_POSTCARD, temp_postcard);

            free(stransi);
            free(lptsTemp);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDC_CONFIGURE_ISARTC:
                    temp_isartc = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_ISARTC)];
                    temp_deviceconfig |= deviceconfig_open(hdlg, (void *) isartc_get_device(temp_isartc));
                    break;

                case IDC_COMBO_ISARTC:
                    temp_isartc = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_ISARTC)];
                    settings_enable_window(hdlg, IDC_CONFIGURE_ISARTC, temp_isartc != 0);
                    break;

                case IDC_COMBO_ISAMEM_1:
                case IDC_COMBO_ISAMEM_2:
                case IDC_COMBO_ISAMEM_3:
                case IDC_COMBO_ISAMEM_4:
                    c              = LOWORD(wParam) - IDC_COMBO_ISAMEM_1;
                    temp_isamem[c] = settings_list_to_device[0][settings_get_cur_sel(hdlg, LOWORD(wParam))];
                    settings_enable_window(hdlg, IDC_CONFIGURE_ISAMEM_1 + c, temp_isamem[c] != 0);
                    break;

                case IDC_CONFIGURE_ISAMEM_1:
                case IDC_CONFIGURE_ISAMEM_2:
                case IDC_CONFIGURE_ISAMEM_3:
                case IDC_CONFIGURE_ISAMEM_4:
                    c = LOWORD(wParam) - IDC_CONFIGURE_ISAMEM_1;
                    temp_deviceconfig |= deviceconfig_inst_open(hdlg, (void *) isamem_get_device(temp_isamem[c]), c + 1);
                    break;
            }
            return FALSE;

        case WM_SAVESETTINGS:
            temp_isartc = settings_list_to_device[1][settings_get_cur_sel(hdlg, IDC_COMBO_ISARTC)];
            for (c = 0; c < ISAMEM_MAX; c++) {
                temp_isamem[c] = settings_list_to_device[0][settings_get_cur_sel(hdlg, IDC_COMBO_ISAMEM_1 + c)];
            }
            temp_bugger   = settings_get_check(hdlg, IDC_CHECK_BUGGER);
            temp_postcard = settings_get_check(hdlg, IDC_CHECK_POSTCARD);

        default:
            return FALSE;
    }
    return FALSE;
}

void
win_settings_show_child(HWND hwndParent, DWORD child_id)
{
    if (child_id == displayed_category)
        return;
    else
        displayed_category = child_id;

    SendMessage(hwndChildDialog, WM_SAVESETTINGS, 0, 0);

    DestroyWindow(hwndChildDialog);

    switch (child_id) {
        case SETTINGS_PAGE_MACHINE:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_MACHINE, hwndParent, win_settings_machine_proc);
            break;
        case SETTINGS_PAGE_VIDEO:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_VIDEO, hwndParent, win_settings_video_proc);
            break;
        case SETTINGS_PAGE_INPUT:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_INPUT, hwndParent, win_settings_input_proc);
            break;
        case SETTINGS_PAGE_SOUND:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_SOUND, hwndParent, win_settings_sound_proc);
            break;
        case SETTINGS_PAGE_NETWORK:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_NETWORK, hwndParent, win_settings_network_proc);
            break;
        case SETTINGS_PAGE_PORTS:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_PORTS, hwndParent, win_settings_ports_proc);
            break;
        case SETTINGS_PAGE_STORAGE:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_STORAGE, hwndParent, win_settings_storage_proc);
            break;
        case SETTINGS_PAGE_HARD_DISKS:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_HARD_DISKS, hwndParent, win_settings_hard_disks_proc);
            break;
        case SETTINGS_PAGE_FLOPPY_AND_CDROM_DRIVES:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_FLOPPY_AND_CDROM_DRIVES, hwndParent, win_settings_floppy_and_cdrom_drives_proc);
            break;
        case SETTINGS_PAGE_OTHER_REMOVABLE_DEVICES:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_OTHER_REMOVABLE_DEVICES, hwndParent, win_settings_other_removable_devices_proc);
            break;
        case SETTINGS_PAGE_PERIPHERALS:
            hwndChildDialog = CreateDialog(hinstance, (LPCWSTR) DLG_CFG_PERIPHERALS, hwndParent, win_settings_peripherals_proc);
            break;
        default:
            fatal("Invalid child dialog ID\n");
            return;
    }

    ShowWindow(hwndChildDialog, SW_SHOWNORMAL);
}

static BOOL
win_settings_main_insert_categories(HWND hwndList)
{
    LVITEM lvI;

    lvI.mask      = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    lvI.stateMask = lvI.iSubItem = lvI.state = 0;

    for (uint8_t i = 0; i < 11; i++) {
        lvI.pszText = plat_get_string(IDS_2065 + i);
        lvI.iItem   = i;
        lvI.iImage  = i;

        if (ListView_InsertItem(hwndList, &lvI) == -1)
            return FALSE;
    }

    return TRUE;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_confirm(HWND hdlg)
{
    int i;

    SendMessage(hwndChildDialog, WM_SAVESETTINGS, 0, 0);

    if (win_settings_changed()) {
        if (confirm_save && !settings_only)
            i = settings_msgbox_ex(MBX_QUESTION_OK | MBX_WARNING | MBX_DONTASK, (wchar_t *) IDS_2122, (wchar_t *) IDS_2123, (wchar_t *) IDS_2124, NULL, NULL);
        else
            i = 0;

        if (i == 10) {
            confirm_save = 0;
            i            = 0;
        }

        if (i == 0)
            win_settings_save();
        else
            return FALSE;
    }

    DestroyWindow(hwndChildDialog);
    EndDialog(hdlg, 0);
    win_notify_dlg_closed();
    return TRUE;
}

static void
win_settings_categories_resize_columns(HWND hdlg)
{
    HWND hwndList = GetDlgItem(hdlg, IDC_SETTINGSCATLIST);
    RECT r;

    GetWindowRect(hwndList, &r);
    ListView_SetColumnWidth(hwndList, 0, (r.right - r.left) + 1 - 5);
}

static BOOL
win_settings_categories_init_columns(HWND hdlg)
{
    LVCOLUMN lvc;
    int      iCol     = 0;
    HWND     hwndList = GetDlgItem(hdlg, IDC_SETTINGSCATLIST);

    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

    lvc.iSubItem = 0;
    lvc.pszText  = plat_get_string(2048);

    lvc.cx  = 171;
    lvc.fmt = LVCFMT_LEFT;

    if (ListView_InsertColumn(hwndList, iCol, &lvc) == -1)
        return FALSE;

    win_settings_categories_resize_columns(hdlg);
    return TRUE;
}

#if defined(__amd64__) || defined(__aarch64__)
static LRESULT CALLBACK
#else
static BOOL CALLBACK
#endif
win_settings_main_proc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    HWND          h = NULL;
    int           category;
    int           j = 0;
    const uint8_t cat_icons[12] = { 240, 241, 242, 243, 96, 244, 252, 80, 246, 247, 245, 0 };

    hwndParentDialog = hdlg;

    switch (message) {
        case WM_INITDIALOG:
            dpi = win_get_dpi(hdlg);
            win_settings_init();
            displayed_category = -1;
            h                  = GetDlgItem(hdlg, IDC_SETTINGSCATLIST);
            win_settings_categories_init_columns(hdlg);
            image_list_init(hdlg, IDC_SETTINGSCATLIST, (const uint8_t *) cat_icons);
            win_settings_main_insert_categories(h);
            settings_listview_select(hdlg, IDC_SETTINGSCATLIST, first_cat);
            settings_listview_enable_styles(hdlg, IDC_SETTINGSCATLIST);
            return TRUE;
        case WM_NOTIFY:
            if ((((LPNMHDR) lParam)->code == LVN_ITEMCHANGED) && (((LPNMHDR) lParam)->idFrom == IDC_SETTINGSCATLIST)) {
                category = -1;
                for (uint8_t i = 0; i < 11; i++) {
                    h = GetDlgItem(hdlg, IDC_SETTINGSCATLIST);
                    j = ListView_GetItemState(h, i, LVIS_SELECTED);
                    if (j)
                        category = i;
                }
                if (category != -1)
                    win_settings_show_child(hdlg, category);
            }
            break;
        case WM_CLOSE:
            DestroyWindow(hwndChildDialog);
            EndDialog(hdlg, 0);
            win_notify_dlg_closed();
            return TRUE;
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    return win_settings_confirm(hdlg);
                case IDCANCEL:
                    DestroyWindow(hwndChildDialog);
                    EndDialog(hdlg, 0);
                    win_notify_dlg_closed();
                    return TRUE;
            }
            break;

        case WM_DPICHANGED:
            dpi = HIWORD(wParam);
            win_settings_categories_resize_columns(hdlg);
            image_list_init(hdlg, IDC_SETTINGSCATLIST, (const uint8_t *) cat_icons);
            break;
        default:
            return FALSE;
    }

    return FALSE;
}

void
win_settings_open_ex(HWND hwnd, int category)
{
    win_notify_dlg_open();

    first_cat = category;
    DialogBox(hinstance, (LPCWSTR) DLG_CONFIG, hwnd, win_settings_main_proc);
}

void
win_settings_open(HWND hwnd)
{
    win_settings_open_ex(hwnd, SETTINGS_PAGE_MACHINE);
}
